blob: 0c5e614b4e97f0d71c332b08cdb98e625e69d9fc [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
Ben Wagnerb607a8f2018-03-12 13:46:21 -04008#include "FakeStreams.h"
9#include "Resources.h"
scroggo8e6c7ad2016-09-16 08:20:38 -070010#include "SkBitmap.h"
11#include "SkCodec.h"
12#include "SkData.h"
13#include "SkImageInfo.h"
Mike Reedede7bac2017-07-23 15:30:02 -040014#include "SkMakeUnique.h"
Ben Wagnerb607a8f2018-03-12 13:46:21 -040015#include "SkRefCnt.h"
16#include "SkStream.h"
17#include "SkTypes.h"
Ben Wagner501c17c2018-03-12 20:04:31 +000018#include "Test.h"
Ben Wagner1a462bd2018-03-12 13:46:21 -040019
Ben Wagnerb607a8f2018-03-12 13:46:21 -040020#include <cstring>
21#include <memory>
22#include <utility>
23#include <vector>
24
scroggo8e6c7ad2016-09-16 08:20:38 -070025static SkImageInfo standardize_info(SkCodec* codec) {
26 SkImageInfo defaultInfo = codec->getInfo();
27 // Note: This drops the SkColorSpace, allowing the equality check between two
28 // different codecs created from the same file to have the same SkImageInfo.
29 return SkImageInfo::MakeN32Premul(defaultInfo.width(), defaultInfo.height());
30}
31
32static bool create_truth(sk_sp<SkData> data, SkBitmap* dst) {
Mike Reedede7bac2017-07-23 15:30:02 -040033 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(std::move(data)));
scroggo8e6c7ad2016-09-16 08:20:38 -070034 if (!codec) {
35 return false;
36 }
37
Ben Wagner145dbcd2016-11-03 14:40:50 -040038 const SkImageInfo info = standardize_info(codec.get());
scroggo8e6c7ad2016-09-16 08:20:38 -070039 dst->allocPixels(info);
40 return SkCodec::kSuccess == codec->getPixels(info, dst->getPixels(), dst->rowBytes());
41}
42
scroggo19b91532016-10-24 09:03:26 -070043static void compare_bitmaps(skiatest::Reporter* r, const SkBitmap& bm1, const SkBitmap& bm2) {
44 const SkImageInfo& info = bm1.info();
45 if (info != bm2.info()) {
46 ERRORF(r, "Bitmaps have different image infos!");
47 return;
48 }
49 const size_t rowBytes = info.minRowBytes();
50 for (int i = 0; i < info.height(); i++) {
nagarajan.n0ec0bf02017-10-11 10:41:27 +053051 if (memcmp(bm1.getAddr(0, i), bm2.getAddr(0, i), rowBytes)) {
52 ERRORF(r, "Bitmaps have different pixels, starting on line %i!", i);
53 return;
54 }
scroggo19b91532016-10-24 09:03:26 -070055 }
56}
57
Leon Scroggins III4993b952016-12-08 11:54:04 -050058static void test_partial(skiatest::Reporter* r, const char* name, size_t minBytes = 0) {
Leon Scroggins IIIe4ba1052017-01-30 13:55:14 -050059 sk_sp<SkData> file = GetResourceAsData(name);
scroggo8e6c7ad2016-09-16 08:20:38 -070060 if (!file) {
61 SkDebugf("missing resource %s\n", name);
62 return;
63 }
64
65 SkBitmap truth;
66 if (!create_truth(file, &truth)) {
67 ERRORF(r, "Failed to decode %s\n", name);
68 return;
69 }
70
scroggo8e6c7ad2016-09-16 08:20:38 -070071 // Now decode part of the file
Leon Scroggins III4993b952016-12-08 11:54:04 -050072 HaltingStream* stream = new HaltingStream(file, SkTMax(file->size() / 2, minBytes));
scroggo8e6c7ad2016-09-16 08:20:38 -070073
74 // Note that we cheat and hold on to a pointer to stream, though it is owned by
75 // partialCodec.
Mike Reedede7bac2017-07-23 15:30:02 -040076 std::unique_ptr<SkCodec> partialCodec(SkCodec::MakeFromStream(std::unique_ptr<SkStream>(stream)));
scroggo8e6c7ad2016-09-16 08:20:38 -070077 if (!partialCodec) {
78 // Technically, this could be a small file where half the file is not
79 // enough.
80 ERRORF(r, "Failed to create codec for %s", name);
81 return;
82 }
83
Ben Wagner145dbcd2016-11-03 14:40:50 -040084 const SkImageInfo info = standardize_info(partialCodec.get());
scroggo8e6c7ad2016-09-16 08:20:38 -070085 SkASSERT(info == truth.info());
86 SkBitmap incremental;
87 incremental.allocPixels(info);
88
scroggo19b91532016-10-24 09:03:26 -070089 while (true) {
90 const SkCodec::Result startResult = partialCodec->startIncrementalDecode(info,
91 incremental.getPixels(), incremental.rowBytes());
92 if (startResult == SkCodec::kSuccess) {
93 break;
94 }
95
96 if (stream->isAllDataReceived()) {
97 ERRORF(r, "Failed to start incremental decode\n");
98 return;
99 }
100
101 // Append some data. The size is arbitrary, but deliberately different from
102 // the buffer size used by SkPngCodec.
103 stream->addNewData(1000);
scroggo8e6c7ad2016-09-16 08:20:38 -0700104 }
105
106 while (true) {
107 const SkCodec::Result result = partialCodec->incrementalDecode();
108
scroggo19b91532016-10-24 09:03:26 -0700109 if (result == SkCodec::kSuccess) {
scroggo8e6c7ad2016-09-16 08:20:38 -0700110 break;
111 }
112
scroggo8e6c7ad2016-09-16 08:20:38 -0700113 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
114
scroggo19b91532016-10-24 09:03:26 -0700115 if (stream->isAllDataReceived()) {
116 ERRORF(r, "Failed to completely decode %s", name);
117 return;
118 }
119
120 // Append some data. The size is arbitrary, but deliberately different from
121 // the buffer size used by SkPngCodec.
122 stream->addNewData(1000);
scroggo8e6c7ad2016-09-16 08:20:38 -0700123 }
124
125 // compare to original
scroggo19b91532016-10-24 09:03:26 -0700126 compare_bitmaps(r, truth, incremental);
scroggo8e6c7ad2016-09-16 08:20:38 -0700127}
128
129DEF_TEST(Codec_partial, r) {
Leon Scroggins III83239652017-04-21 13:47:12 -0400130#if 0
131 // FIXME (scroggo): SkPngCodec needs to use SkStreamBuffer in order to
132 // support incremental decoding.
Hal Canaryc465d132017-12-08 10:21:31 -0500133 test_partial(r, "images/plane.png");
134 test_partial(r, "images/plane_interlaced.png");
135 test_partial(r, "images/yellow_rose.png");
136 test_partial(r, "images/index8.png");
137 test_partial(r, "images/color_wheel.png");
138 test_partial(r, "images/mandrill_256.png");
139 test_partial(r, "images/mandrill_32.png");
140 test_partial(r, "images/arrow.png");
141 test_partial(r, "images/randPixels.png");
142 test_partial(r, "images/baby_tux.png");
Leon Scroggins III83239652017-04-21 13:47:12 -0400143#endif
Hal Canaryc465d132017-12-08 10:21:31 -0500144 test_partial(r, "images/box.gif");
145 test_partial(r, "images/randPixels.gif", 215);
146 test_partial(r, "images/color_wheel.gif");
scroggo19b91532016-10-24 09:03:26 -0700147}
148
Leon Scroggins IIIe4ba1052017-01-30 13:55:14 -0500149// Verify that when decoding an animated gif byte by byte we report the correct
150// fRequiredFrame as soon as getFrameInfo reports the frame.
151DEF_TEST(Codec_requiredFrame, r) {
Hal Canaryc465d132017-12-08 10:21:31 -0500152 auto path = "images/colorTables.gif";
Leon Scroggins IIIe4ba1052017-01-30 13:55:14 -0500153 sk_sp<SkData> file = GetResourceAsData(path);
154 if (!file) {
155 return;
156 }
157
Mike Reedede7bac2017-07-23 15:30:02 -0400158 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(file));
Leon Scroggins IIIe4ba1052017-01-30 13:55:14 -0500159 if (!codec) {
160 ERRORF(r, "Failed to create codec from %s", path);
161 return;
162 }
163
164 auto frameInfo = codec->getFrameInfo();
165 if (frameInfo.size() <= 1) {
166 ERRORF(r, "Test is uninteresting with 0 or 1 frames");
167 return;
168 }
169
170 HaltingStream* stream(nullptr);
171 std::unique_ptr<SkCodec> partialCodec(nullptr);
172 for (size_t i = 0; !partialCodec; i++) {
173 if (file->size() == i) {
174 ERRORF(r, "Should have created a partial codec for %s", path);
175 return;
176 }
177 stream = new HaltingStream(file, i);
Mike Reedede7bac2017-07-23 15:30:02 -0400178 partialCodec = SkCodec::MakeFromStream(std::unique_ptr<SkStream>(stream));
Leon Scroggins IIIe4ba1052017-01-30 13:55:14 -0500179 }
180
181 std::vector<SkCodec::FrameInfo> partialInfo;
182 size_t frameToCompare = 0;
183 for (; stream->getLength() <= file->size(); stream->addNewData(1)) {
184 partialInfo = partialCodec->getFrameInfo();
185 for (; frameToCompare < partialInfo.size(); frameToCompare++) {
186 REPORTER_ASSERT(r, partialInfo[frameToCompare].fRequiredFrame
187 == frameInfo[frameToCompare].fRequiredFrame);
188 }
189
190 if (frameToCompare == frameInfo.size()) {
191 break;
192 }
193 }
194}
195
scroggo19b91532016-10-24 09:03:26 -0700196DEF_TEST(Codec_partialAnim, r) {
Hal Canaryc465d132017-12-08 10:21:31 -0500197 auto path = "images/test640x479.gif";
Leon Scroggins IIIe4ba1052017-01-30 13:55:14 -0500198 sk_sp<SkData> file = GetResourceAsData(path);
scroggo19b91532016-10-24 09:03:26 -0700199 if (!file) {
200 return;
201 }
202
203 // This stream will be owned by fullCodec, but we hang on to the pointer
204 // to determine frame offsets.
Mike Reedede7bac2017-07-23 15:30:02 -0400205 std::unique_ptr<SkCodec> fullCodec(SkCodec::MakeFromStream(skstd::make_unique<SkMemoryStream>(file)));
scroggo19b91532016-10-24 09:03:26 -0700206 const auto info = standardize_info(fullCodec.get());
207
208 // frameByteCounts stores the number of bytes to decode a particular frame.
209 // - [0] is the number of bytes for the header
210 // - frames[i] requires frameByteCounts[i+1] bytes to decode
Leon Scroggins III1f6af6b2017-06-12 16:41:09 -0400211 const std::vector<size_t> frameByteCounts = { 455, 69350, 1344, 1346, 1327 };
scroggo19b91532016-10-24 09:03:26 -0700212 std::vector<SkBitmap> frames;
scroggo19b91532016-10-24 09:03:26 -0700213 for (size_t i = 0; true; i++) {
scroggo19b91532016-10-24 09:03:26 -0700214 SkBitmap frame;
215 frame.allocPixels(info);
216
217 SkCodec::Options opts;
218 opts.fFrameIndex = i;
219 const SkCodec::Result result = fullCodec->getPixels(info, frame.getPixels(),
Leon Scroggins571b30f2017-07-11 17:35:31 +0000220 frame.rowBytes(), &opts);
scroggo19b91532016-10-24 09:03:26 -0700221
Leon Scroggins III3fc97d72016-12-09 16:39:33 -0500222 if (result == SkCodec::kIncompleteInput || result == SkCodec::kInvalidInput) {
scroggo19b91532016-10-24 09:03:26 -0700223 // We need to distinguish between a partial frame and no more frames.
224 // getFrameInfo lets us do this, since it tells the number of frames
225 // not considering whether they are complete.
226 // FIXME: Should we use a different Result?
227 if (fullCodec->getFrameInfo().size() > i) {
228 // This is a partial frame.
229 frames.push_back(frame);
230 }
231 break;
232 }
233
234 if (result != SkCodec::kSuccess) {
235 ERRORF(r, "Failed to decode frame %i from %s", i, path);
236 return;
237 }
238
239 frames.push_back(frame);
240 }
241
242 // Now decode frames partially, then completely, and compare to the original.
243 HaltingStream* haltingStream = new HaltingStream(file, frameByteCounts[0]);
Mike Reedede7bac2017-07-23 15:30:02 -0400244 std::unique_ptr<SkCodec> partialCodec(SkCodec::MakeFromStream(
245 std::unique_ptr<SkStream>(haltingStream)));
scroggo19b91532016-10-24 09:03:26 -0700246 if (!partialCodec) {
247 ERRORF(r, "Failed to create a partial codec from %s with %i bytes out of %i",
248 path, frameByteCounts[0], file->size());
249 return;
250 }
251
252 SkASSERT(frameByteCounts.size() > frames.size());
253 for (size_t i = 0; i < frames.size(); i++) {
254 const size_t fullFrameBytes = frameByteCounts[i + 1];
255 const size_t firstHalf = fullFrameBytes / 2;
256 const size_t secondHalf = fullFrameBytes - firstHalf;
257
258 haltingStream->addNewData(firstHalf);
Leon Scroggins III3639faa2016-12-08 11:38:58 -0500259 auto frameInfo = partialCodec->getFrameInfo();
260 REPORTER_ASSERT(r, frameInfo.size() == i + 1);
261 REPORTER_ASSERT(r, !frameInfo[i].fFullyReceived);
scroggo19b91532016-10-24 09:03:26 -0700262
263 SkBitmap frame;
264 frame.allocPixels(info);
265
266 SkCodec::Options opts;
267 opts.fFrameIndex = i;
268 SkCodec::Result result = partialCodec->startIncrementalDecode(info,
269 frame.getPixels(), frame.rowBytes(), &opts);
270 if (result != SkCodec::kSuccess) {
271 ERRORF(r, "Failed to start incremental decode for %s on frame %i",
272 path, i);
273 return;
274 }
275
276 result = partialCodec->incrementalDecode();
277 REPORTER_ASSERT(r, SkCodec::kIncompleteInput == result);
278
279 haltingStream->addNewData(secondHalf);
280 result = partialCodec->incrementalDecode();
281 REPORTER_ASSERT(r, SkCodec::kSuccess == result);
282
Leon Scroggins III3639faa2016-12-08 11:38:58 -0500283 frameInfo = partialCodec->getFrameInfo();
284 REPORTER_ASSERT(r, frameInfo.size() == i + 1);
285 REPORTER_ASSERT(r, frameInfo[i].fFullyReceived);
scroggo19b91532016-10-24 09:03:26 -0700286 compare_bitmaps(r, frames[i], frame);
287 }
scroggo8e6c7ad2016-09-16 08:20:38 -0700288}
289
290// Test that calling getPixels when an incremental decode has been
291// started (but not finished) makes the next call to incrementalDecode
292// require a call to startIncrementalDecode.
293static void test_interleaved(skiatest::Reporter* r, const char* name) {
Leon Scroggins IIIe4ba1052017-01-30 13:55:14 -0500294 sk_sp<SkData> file = GetResourceAsData(name);
scroggo19b91532016-10-24 09:03:26 -0700295 if (!file) {
296 return;
297 }
298 const size_t halfSize = file->size() / 2;
Mike Reedede7bac2017-07-23 15:30:02 -0400299 std::unique_ptr<SkCodec> partialCodec(SkCodec::MakeFromStream(
300 skstd::make_unique<HaltingStream>(std::move(file), halfSize)));
scroggo8e6c7ad2016-09-16 08:20:38 -0700301 if (!partialCodec) {
302 ERRORF(r, "Failed to create codec for %s", name);
303 return;
304 }
305
Ben Wagner145dbcd2016-11-03 14:40:50 -0400306 const SkImageInfo info = standardize_info(partialCodec.get());
scroggo8e6c7ad2016-09-16 08:20:38 -0700307 SkBitmap incremental;
308 incremental.allocPixels(info);
309
310 const SkCodec::Result startResult = partialCodec->startIncrementalDecode(info,
311 incremental.getPixels(), incremental.rowBytes());
312 if (startResult != SkCodec::kSuccess) {
313 ERRORF(r, "Failed to start incremental decode\n");
314 return;
315 }
316
317 SkCodec::Result result = partialCodec->incrementalDecode();
318 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
319
320 SkBitmap full;
321 full.allocPixels(info);
322 result = partialCodec->getPixels(info, full.getPixels(), full.rowBytes());
323 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
324
325 // Now incremental decode will fail
326 result = partialCodec->incrementalDecode();
327 REPORTER_ASSERT(r, result == SkCodec::kInvalidParameters);
328}
329
330DEF_TEST(Codec_rewind, r) {
Hal Canaryc465d132017-12-08 10:21:31 -0500331 test_interleaved(r, "images/plane.png");
332 test_interleaved(r, "images/plane_interlaced.png");
333 test_interleaved(r, "images/box.gif");
scroggo8e6c7ad2016-09-16 08:20:38 -0700334}
Leon Scroggins III3fc97d72016-12-09 16:39:33 -0500335
336// Modified version of the giflib logo, from
337// http://giflib.sourceforge.net/whatsinagif/bits_and_bytes.html
338// The global color map has been replaced with a local color map.
339static unsigned char gNoGlobalColorMap[] = {
340 // Header
341 0x47, 0x49, 0x46, 0x38, 0x39, 0x61,
342
343 // Logical screen descriptor
344 0x0A, 0x00, 0x0A, 0x00, 0x11, 0x00, 0x00,
345
346 // Image descriptor
347 0x2C, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x81,
348
349 // Local color table
350 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00,
351
352 // Image data
353 0x02, 0x16, 0x8C, 0x2D, 0x99, 0x87, 0x2A, 0x1C, 0xDC, 0x33, 0xA0, 0x02, 0x75,
354 0xEC, 0x95, 0xFA, 0xA8, 0xDE, 0x60, 0x8C, 0x04, 0x91, 0x4C, 0x01, 0x00,
355
356 // Trailer
357 0x3B,
358};
359
360// Test that a gif file truncated before its local color map behaves as expected.
361DEF_TEST(Codec_GifPreMap, r) {
362 sk_sp<SkData> data = SkData::MakeWithoutCopy(gNoGlobalColorMap, sizeof(gNoGlobalColorMap));
Mike Reedede7bac2017-07-23 15:30:02 -0400363 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(data));
Leon Scroggins III3fc97d72016-12-09 16:39:33 -0500364 if (!codec) {
365 ERRORF(r, "failed to create codec");
366 return;
367 }
368
369 SkBitmap truth;
370 auto info = standardize_info(codec.get());
371 truth.allocPixels(info);
372
373 auto result = codec->getPixels(info, truth.getPixels(), truth.rowBytes());
374 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
375
376 // Truncate to 23 bytes, just before the color map. This should fail to decode.
Mike Reedede7bac2017-07-23 15:30:02 -0400377 codec = SkCodec::MakeFromData(SkData::MakeWithoutCopy(gNoGlobalColorMap, 23));
Leon Scroggins III3fc97d72016-12-09 16:39:33 -0500378 REPORTER_ASSERT(r, codec);
379 if (codec) {
380 SkBitmap bm;
381 bm.allocPixels(info);
382 result = codec->getPixels(info, bm.getPixels(), bm.rowBytes());
383 REPORTER_ASSERT(r, result == SkCodec::kInvalidInput);
384 }
385
386 // Again, truncate to 23 bytes, this time for an incremental decode. We
387 // cannot start an incremental decode until we have more data. If we did,
388 // we would be using the wrong color table.
389 HaltingStream* stream = new HaltingStream(data, 23);
Mike Reedede7bac2017-07-23 15:30:02 -0400390 codec = SkCodec::MakeFromStream(std::unique_ptr<SkStream>(stream));
Leon Scroggins III3fc97d72016-12-09 16:39:33 -0500391 REPORTER_ASSERT(r, codec);
392 if (codec) {
393 SkBitmap bm;
394 bm.allocPixels(info);
395 result = codec->startIncrementalDecode(info, bm.getPixels(), bm.rowBytes());
396 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
397
398 stream->addNewData(data->size());
399 result = codec->startIncrementalDecode(info, bm.getPixels(), bm.rowBytes());
400 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
401
402 result = codec->incrementalDecode();
403 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
404 compare_bitmaps(r, truth, bm);
405 }
406}
Leon Scroggins IIIb6446502017-04-24 09:32:50 -0400407
408DEF_TEST(Codec_emptyIDAT, r) {
Hal Canaryc465d132017-12-08 10:21:31 -0500409 const char* name = "images/baby_tux.png";
Leon Scroggins IIIb6446502017-04-24 09:32:50 -0400410 sk_sp<SkData> file = GetResourceAsData(name);
411 if (!file) {
Leon Scroggins IIIb6446502017-04-24 09:32:50 -0400412 return;
413 }
414
415 // Truncate to the beginning of the IDAT, immediately after the IDAT tag.
416 file = SkData::MakeSubset(file.get(), 0, 80);
417
Mike Reedede7bac2017-07-23 15:30:02 -0400418 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(std::move(file)));
Leon Scroggins IIIb6446502017-04-24 09:32:50 -0400419 if (!codec) {
420 ERRORF(r, "Failed to create a codec for %s", name);
421 return;
422 }
423
424 SkBitmap bm;
425 const auto info = standardize_info(codec.get());
426 bm.allocPixels(info);
427
428 const auto result = codec->getPixels(info, bm.getPixels(), bm.rowBytes());
429 REPORTER_ASSERT(r, SkCodec::kIncompleteInput == result);
430}
Leon Scroggins III588fb042017-07-14 16:32:31 -0400431
432DEF_TEST(Codec_incomplete, r) {
Hal Canaryc465d132017-12-08 10:21:31 -0500433 for (const char* name : { "images/baby_tux.png",
434 "images/baby_tux.webp",
435 "images/CMYK.jpg",
436 "images/color_wheel.gif",
437 "images/google_chrome.ico",
438 "images/rle.bmp",
439 "images/mandrill.wbmp",
Leon Scroggins III588fb042017-07-14 16:32:31 -0400440 }) {
441 sk_sp<SkData> file = GetResourceAsData(name);
Bruce Wang61f36d32018-05-29 17:29:24 -0400442 if (!file) {
Leon Scroggins III588fb042017-07-14 16:32:31 -0400443 continue;
444 }
445
446 for (size_t len = 14; len <= file->size(); len += 5) {
447 SkCodec::Result result;
Mike Reedede7bac2017-07-23 15:30:02 -0400448 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromStream(
449 skstd::make_unique<SkMemoryStream>(file->data(), len), &result));
Leon Scroggins III588fb042017-07-14 16:32:31 -0400450 if (codec) {
451 if (result != SkCodec::kSuccess) {
452 ERRORF(r, "Created an SkCodec for %s with %lu bytes, but "
453 "reported an error %i", name, len, result);
454 }
455 break;
456 }
457
458 if (SkCodec::kIncompleteInput != result) {
459 ERRORF(r, "Reported error %i for %s with %lu bytes",
460 result, name, len);
461 break;
462 }
463 }
464 }
465}