blob: 975ca63ee9ef215022c2dbab0380e144ba9c2f86 [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"
12#include "SkRWBuffer.h"
13#include "SkString.h"
14
Leon Scroggins III932efed2016-12-16 11:39:51 -050015#include "FakeStreams.h"
scroggo8e6c7ad2016-09-16 08:20:38 -070016#include "Resources.h"
17#include "Test.h"
18
scroggo8e6c7ad2016-09-16 08:20:38 -070019static SkImageInfo standardize_info(SkCodec* codec) {
20 SkImageInfo defaultInfo = codec->getInfo();
21 // Note: This drops the SkColorSpace, allowing the equality check between two
22 // different codecs created from the same file to have the same SkImageInfo.
23 return SkImageInfo::MakeN32Premul(defaultInfo.width(), defaultInfo.height());
24}
25
26static bool create_truth(sk_sp<SkData> data, SkBitmap* dst) {
Ben Wagner145dbcd2016-11-03 14:40:50 -040027 std::unique_ptr<SkCodec> codec(SkCodec::NewFromData(std::move(data)));
scroggo8e6c7ad2016-09-16 08:20:38 -070028 if (!codec) {
29 return false;
30 }
31
Ben Wagner145dbcd2016-11-03 14:40:50 -040032 const SkImageInfo info = standardize_info(codec.get());
scroggo8e6c7ad2016-09-16 08:20:38 -070033 dst->allocPixels(info);
34 return SkCodec::kSuccess == codec->getPixels(info, dst->getPixels(), dst->rowBytes());
35}
36
scroggo19b91532016-10-24 09:03:26 -070037static void compare_bitmaps(skiatest::Reporter* r, const SkBitmap& bm1, const SkBitmap& bm2) {
38 const SkImageInfo& info = bm1.info();
39 if (info != bm2.info()) {
40 ERRORF(r, "Bitmaps have different image infos!");
41 return;
42 }
43 const size_t rowBytes = info.minRowBytes();
44 for (int i = 0; i < info.height(); i++) {
45 REPORTER_ASSERT(r, !memcmp(bm1.getAddr(0, 0), bm2.getAddr(0, 0), rowBytes));
46 }
47}
48
Leon Scroggins III4993b952016-12-08 11:54:04 -050049static void test_partial(skiatest::Reporter* r, const char* name, size_t minBytes = 0) {
Leon Scroggins IIIe4ba1052017-01-30 13:55:14 -050050 sk_sp<SkData> file = GetResourceAsData(name);
scroggo8e6c7ad2016-09-16 08:20:38 -070051 if (!file) {
52 SkDebugf("missing resource %s\n", name);
53 return;
54 }
55
56 SkBitmap truth;
57 if (!create_truth(file, &truth)) {
58 ERRORF(r, "Failed to decode %s\n", name);
59 return;
60 }
61
scroggo8e6c7ad2016-09-16 08:20:38 -070062 // Now decode part of the file
Leon Scroggins III4993b952016-12-08 11:54:04 -050063 HaltingStream* stream = new HaltingStream(file, SkTMax(file->size() / 2, minBytes));
scroggo8e6c7ad2016-09-16 08:20:38 -070064
65 // Note that we cheat and hold on to a pointer to stream, though it is owned by
66 // partialCodec.
Ben Wagner145dbcd2016-11-03 14:40:50 -040067 std::unique_ptr<SkCodec> partialCodec(SkCodec::NewFromStream(stream));
scroggo8e6c7ad2016-09-16 08:20:38 -070068 if (!partialCodec) {
69 // Technically, this could be a small file where half the file is not
70 // enough.
71 ERRORF(r, "Failed to create codec for %s", name);
72 return;
73 }
74
Ben Wagner145dbcd2016-11-03 14:40:50 -040075 const SkImageInfo info = standardize_info(partialCodec.get());
scroggo8e6c7ad2016-09-16 08:20:38 -070076 SkASSERT(info == truth.info());
77 SkBitmap incremental;
78 incremental.allocPixels(info);
79
scroggo19b91532016-10-24 09:03:26 -070080 while (true) {
81 const SkCodec::Result startResult = partialCodec->startIncrementalDecode(info,
82 incremental.getPixels(), incremental.rowBytes());
83 if (startResult == SkCodec::kSuccess) {
84 break;
85 }
86
87 if (stream->isAllDataReceived()) {
88 ERRORF(r, "Failed to start incremental decode\n");
89 return;
90 }
91
92 // Append some data. The size is arbitrary, but deliberately different from
93 // the buffer size used by SkPngCodec.
94 stream->addNewData(1000);
scroggo8e6c7ad2016-09-16 08:20:38 -070095 }
96
97 while (true) {
98 const SkCodec::Result result = partialCodec->incrementalDecode();
99
scroggo19b91532016-10-24 09:03:26 -0700100 if (result == SkCodec::kSuccess) {
scroggo8e6c7ad2016-09-16 08:20:38 -0700101 break;
102 }
103
scroggo8e6c7ad2016-09-16 08:20:38 -0700104 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
105
scroggo19b91532016-10-24 09:03:26 -0700106 if (stream->isAllDataReceived()) {
107 ERRORF(r, "Failed to completely decode %s", name);
108 return;
109 }
110
111 // Append some data. The size is arbitrary, but deliberately different from
112 // the buffer size used by SkPngCodec.
113 stream->addNewData(1000);
scroggo8e6c7ad2016-09-16 08:20:38 -0700114 }
115
116 // compare to original
scroggo19b91532016-10-24 09:03:26 -0700117 compare_bitmaps(r, truth, incremental);
scroggo8e6c7ad2016-09-16 08:20:38 -0700118}
119
120DEF_TEST(Codec_partial, r) {
Leon Scroggins III83239652017-04-21 13:47:12 -0400121#if 0
122 // FIXME (scroggo): SkPngCodec needs to use SkStreamBuffer in order to
123 // support incremental decoding.
scroggo8e6c7ad2016-09-16 08:20:38 -0700124 test_partial(r, "plane.png");
125 test_partial(r, "plane_interlaced.png");
126 test_partial(r, "yellow_rose.png");
127 test_partial(r, "index8.png");
128 test_partial(r, "color_wheel.png");
129 test_partial(r, "mandrill_256.png");
130 test_partial(r, "mandrill_32.png");
131 test_partial(r, "arrow.png");
132 test_partial(r, "randPixels.png");
133 test_partial(r, "baby_tux.png");
Leon Scroggins III83239652017-04-21 13:47:12 -0400134#endif
scroggo19b91532016-10-24 09:03:26 -0700135 test_partial(r, "box.gif");
Leon Scroggins III4993b952016-12-08 11:54:04 -0500136 test_partial(r, "randPixels.gif", 215);
scroggo19b91532016-10-24 09:03:26 -0700137 test_partial(r, "color_wheel.gif");
138}
139
Leon Scroggins IIIe4ba1052017-01-30 13:55:14 -0500140// Verify that when decoding an animated gif byte by byte we report the correct
141// fRequiredFrame as soon as getFrameInfo reports the frame.
142DEF_TEST(Codec_requiredFrame, r) {
143 auto path = "colorTables.gif";
144 sk_sp<SkData> file = GetResourceAsData(path);
145 if (!file) {
146 return;
147 }
148
149 std::unique_ptr<SkCodec> codec(SkCodec::NewFromData(file));
150 if (!codec) {
151 ERRORF(r, "Failed to create codec from %s", path);
152 return;
153 }
154
155 auto frameInfo = codec->getFrameInfo();
156 if (frameInfo.size() <= 1) {
157 ERRORF(r, "Test is uninteresting with 0 or 1 frames");
158 return;
159 }
160
161 HaltingStream* stream(nullptr);
162 std::unique_ptr<SkCodec> partialCodec(nullptr);
163 for (size_t i = 0; !partialCodec; i++) {
164 if (file->size() == i) {
165 ERRORF(r, "Should have created a partial codec for %s", path);
166 return;
167 }
168 stream = new HaltingStream(file, i);
169 partialCodec.reset(SkCodec::NewFromStream(stream));
170 }
171
172 std::vector<SkCodec::FrameInfo> partialInfo;
173 size_t frameToCompare = 0;
174 for (; stream->getLength() <= file->size(); stream->addNewData(1)) {
175 partialInfo = partialCodec->getFrameInfo();
176 for (; frameToCompare < partialInfo.size(); frameToCompare++) {
177 REPORTER_ASSERT(r, partialInfo[frameToCompare].fRequiredFrame
178 == frameInfo[frameToCompare].fRequiredFrame);
179 }
180
181 if (frameToCompare == frameInfo.size()) {
182 break;
183 }
184 }
185}
186
scroggo19b91532016-10-24 09:03:26 -0700187DEF_TEST(Codec_partialAnim, r) {
188 auto path = "test640x479.gif";
Leon Scroggins IIIe4ba1052017-01-30 13:55:14 -0500189 sk_sp<SkData> file = GetResourceAsData(path);
scroggo19b91532016-10-24 09:03:26 -0700190 if (!file) {
191 return;
192 }
193
194 // This stream will be owned by fullCodec, but we hang on to the pointer
195 // to determine frame offsets.
196 SkStream* stream = new SkMemoryStream(file);
197 std::unique_ptr<SkCodec> fullCodec(SkCodec::NewFromStream(stream));
198 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(),
212 frame.rowBytes(), &opts, nullptr, nullptr);
213
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]);
236 std::unique_ptr<SkCodec> partialCodec(SkCodec::NewFromStream(haltingStream));
237 if (!partialCodec) {
238 ERRORF(r, "Failed to create a partial codec from %s with %i bytes out of %i",
239 path, frameByteCounts[0], file->size());
240 return;
241 }
242
243 SkASSERT(frameByteCounts.size() > frames.size());
244 for (size_t i = 0; i < frames.size(); i++) {
245 const size_t fullFrameBytes = frameByteCounts[i + 1];
246 const size_t firstHalf = fullFrameBytes / 2;
247 const size_t secondHalf = fullFrameBytes - firstHalf;
248
249 haltingStream->addNewData(firstHalf);
Leon Scroggins III3639faa2016-12-08 11:38:58 -0500250 auto frameInfo = partialCodec->getFrameInfo();
251 REPORTER_ASSERT(r, frameInfo.size() == i + 1);
252 REPORTER_ASSERT(r, !frameInfo[i].fFullyReceived);
scroggo19b91532016-10-24 09:03:26 -0700253
254 SkBitmap frame;
255 frame.allocPixels(info);
256
257 SkCodec::Options opts;
258 opts.fFrameIndex = i;
259 SkCodec::Result result = partialCodec->startIncrementalDecode(info,
260 frame.getPixels(), frame.rowBytes(), &opts);
261 if (result != SkCodec::kSuccess) {
262 ERRORF(r, "Failed to start incremental decode for %s on frame %i",
263 path, i);
264 return;
265 }
266
267 result = partialCodec->incrementalDecode();
268 REPORTER_ASSERT(r, SkCodec::kIncompleteInput == result);
269
270 haltingStream->addNewData(secondHalf);
271 result = partialCodec->incrementalDecode();
272 REPORTER_ASSERT(r, SkCodec::kSuccess == result);
273
Leon Scroggins III3639faa2016-12-08 11:38:58 -0500274 frameInfo = partialCodec->getFrameInfo();
275 REPORTER_ASSERT(r, frameInfo.size() == i + 1);
276 REPORTER_ASSERT(r, frameInfo[i].fFullyReceived);
scroggo19b91532016-10-24 09:03:26 -0700277 compare_bitmaps(r, frames[i], frame);
278 }
scroggo8e6c7ad2016-09-16 08:20:38 -0700279}
280
281// Test that calling getPixels when an incremental decode has been
282// started (but not finished) makes the next call to incrementalDecode
283// require a call to startIncrementalDecode.
284static void test_interleaved(skiatest::Reporter* r, const char* name) {
Leon Scroggins IIIe4ba1052017-01-30 13:55:14 -0500285 sk_sp<SkData> file = GetResourceAsData(name);
scroggo19b91532016-10-24 09:03:26 -0700286 if (!file) {
287 return;
288 }
289 const size_t halfSize = file->size() / 2;
Ben Wagner145dbcd2016-11-03 14:40:50 -0400290 std::unique_ptr<SkCodec> partialCodec(SkCodec::NewFromStream(
scroggo19b91532016-10-24 09:03:26 -0700291 new HaltingStream(std::move(file), halfSize)));
scroggo8e6c7ad2016-09-16 08:20:38 -0700292 if (!partialCodec) {
293 ERRORF(r, "Failed to create codec for %s", name);
294 return;
295 }
296
Ben Wagner145dbcd2016-11-03 14:40:50 -0400297 const SkImageInfo info = standardize_info(partialCodec.get());
scroggo8e6c7ad2016-09-16 08:20:38 -0700298 SkBitmap incremental;
299 incremental.allocPixels(info);
300
301 const SkCodec::Result startResult = partialCodec->startIncrementalDecode(info,
302 incremental.getPixels(), incremental.rowBytes());
303 if (startResult != SkCodec::kSuccess) {
304 ERRORF(r, "Failed to start incremental decode\n");
305 return;
306 }
307
308 SkCodec::Result result = partialCodec->incrementalDecode();
309 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
310
311 SkBitmap full;
312 full.allocPixels(info);
313 result = partialCodec->getPixels(info, full.getPixels(), full.rowBytes());
314 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
315
316 // Now incremental decode will fail
317 result = partialCodec->incrementalDecode();
318 REPORTER_ASSERT(r, result == SkCodec::kInvalidParameters);
319}
320
321DEF_TEST(Codec_rewind, r) {
322 test_interleaved(r, "plane.png");
323 test_interleaved(r, "plane_interlaced.png");
scroggo19b91532016-10-24 09:03:26 -0700324 test_interleaved(r, "box.gif");
scroggo8e6c7ad2016-09-16 08:20:38 -0700325}
Leon Scroggins III3fc97d72016-12-09 16:39:33 -0500326
327// Modified version of the giflib logo, from
328// http://giflib.sourceforge.net/whatsinagif/bits_and_bytes.html
329// The global color map has been replaced with a local color map.
330static unsigned char gNoGlobalColorMap[] = {
331 // Header
332 0x47, 0x49, 0x46, 0x38, 0x39, 0x61,
333
334 // Logical screen descriptor
335 0x0A, 0x00, 0x0A, 0x00, 0x11, 0x00, 0x00,
336
337 // Image descriptor
338 0x2C, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x81,
339
340 // Local color table
341 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00,
342
343 // Image data
344 0x02, 0x16, 0x8C, 0x2D, 0x99, 0x87, 0x2A, 0x1C, 0xDC, 0x33, 0xA0, 0x02, 0x75,
345 0xEC, 0x95, 0xFA, 0xA8, 0xDE, 0x60, 0x8C, 0x04, 0x91, 0x4C, 0x01, 0x00,
346
347 // Trailer
348 0x3B,
349};
350
351// Test that a gif file truncated before its local color map behaves as expected.
352DEF_TEST(Codec_GifPreMap, r) {
353 sk_sp<SkData> data = SkData::MakeWithoutCopy(gNoGlobalColorMap, sizeof(gNoGlobalColorMap));
354 std::unique_ptr<SkCodec> codec(SkCodec::NewFromData(data));
355 if (!codec) {
356 ERRORF(r, "failed to create codec");
357 return;
358 }
359
360 SkBitmap truth;
361 auto info = standardize_info(codec.get());
362 truth.allocPixels(info);
363
364 auto result = codec->getPixels(info, truth.getPixels(), truth.rowBytes());
365 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
366
367 // Truncate to 23 bytes, just before the color map. This should fail to decode.
368 codec.reset(SkCodec::NewFromData(SkData::MakeWithoutCopy(gNoGlobalColorMap, 23)));
369 REPORTER_ASSERT(r, codec);
370 if (codec) {
371 SkBitmap bm;
372 bm.allocPixels(info);
373 result = codec->getPixels(info, bm.getPixels(), bm.rowBytes());
374 REPORTER_ASSERT(r, result == SkCodec::kInvalidInput);
375 }
376
377 // Again, truncate to 23 bytes, this time for an incremental decode. We
378 // cannot start an incremental decode until we have more data. If we did,
379 // we would be using the wrong color table.
380 HaltingStream* stream = new HaltingStream(data, 23);
381 codec.reset(SkCodec::NewFromStream(stream));
382 REPORTER_ASSERT(r, codec);
383 if (codec) {
384 SkBitmap bm;
385 bm.allocPixels(info);
386 result = codec->startIncrementalDecode(info, bm.getPixels(), bm.rowBytes());
387 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
388
389 stream->addNewData(data->size());
390 result = codec->startIncrementalDecode(info, bm.getPixels(), bm.rowBytes());
391 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
392
393 result = codec->incrementalDecode();
394 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
395 compare_bitmaps(r, truth, bm);
396 }
397}
Leon Scroggins IIIb6446502017-04-24 09:32:50 -0400398
399DEF_TEST(Codec_emptyIDAT, r) {
400 const char* name = "baby_tux.png";
401 sk_sp<SkData> file = GetResourceAsData(name);
402 if (!file) {
Leon Scroggins IIIb6446502017-04-24 09:32:50 -0400403 return;
404 }
405
406 // Truncate to the beginning of the IDAT, immediately after the IDAT tag.
407 file = SkData::MakeSubset(file.get(), 0, 80);
408
409 std::unique_ptr<SkCodec> codec(SkCodec::NewFromData(std::move(file)));
410 if (!codec) {
411 ERRORF(r, "Failed to create a codec for %s", name);
412 return;
413 }
414
415 SkBitmap bm;
416 const auto info = standardize_info(codec.get());
417 bm.allocPixels(info);
418
419 const auto result = codec->getPixels(info, bm.getPixels(), bm.rowBytes());
420 REPORTER_ASSERT(r, SkCodec::kIncompleteInput == result);
421}