blob: c029922f7150764eb50a36d1a1934af96082da75 [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
203 std::vector<size_t> frameByteCounts;
204 std::vector<SkBitmap> frames;
205 size_t lastOffset = 0;
206 for (size_t i = 0; true; i++) {
207 frameByteCounts.push_back(stream->getPosition() - lastOffset);
208 lastOffset = stream->getPosition();
209
210 SkBitmap frame;
211 frame.allocPixels(info);
212
213 SkCodec::Options opts;
214 opts.fFrameIndex = i;
215 const SkCodec::Result result = fullCodec->getPixels(info, frame.getPixels(),
216 frame.rowBytes(), &opts, nullptr, nullptr);
217
Leon Scroggins III3fc97d72016-12-09 16:39:33 -0500218 if (result == SkCodec::kIncompleteInput || result == SkCodec::kInvalidInput) {
scroggo19b91532016-10-24 09:03:26 -0700219 frameByteCounts.push_back(stream->getPosition() - lastOffset);
220
221 // We need to distinguish between a partial frame and no more frames.
222 // getFrameInfo lets us do this, since it tells the number of frames
223 // not considering whether they are complete.
224 // FIXME: Should we use a different Result?
225 if (fullCodec->getFrameInfo().size() > i) {
226 // This is a partial frame.
227 frames.push_back(frame);
228 }
229 break;
230 }
231
232 if (result != SkCodec::kSuccess) {
233 ERRORF(r, "Failed to decode frame %i from %s", i, path);
234 return;
235 }
236
237 frames.push_back(frame);
238 }
239
240 // Now decode frames partially, then completely, and compare to the original.
241 HaltingStream* haltingStream = new HaltingStream(file, frameByteCounts[0]);
242 std::unique_ptr<SkCodec> partialCodec(SkCodec::NewFromStream(haltingStream));
243 if (!partialCodec) {
244 ERRORF(r, "Failed to create a partial codec from %s with %i bytes out of %i",
245 path, frameByteCounts[0], file->size());
246 return;
247 }
248
249 SkASSERT(frameByteCounts.size() > frames.size());
250 for (size_t i = 0; i < frames.size(); i++) {
251 const size_t fullFrameBytes = frameByteCounts[i + 1];
252 const size_t firstHalf = fullFrameBytes / 2;
253 const size_t secondHalf = fullFrameBytes - firstHalf;
254
255 haltingStream->addNewData(firstHalf);
Leon Scroggins III3639faa2016-12-08 11:38:58 -0500256 auto frameInfo = partialCodec->getFrameInfo();
257 REPORTER_ASSERT(r, frameInfo.size() == i + 1);
258 REPORTER_ASSERT(r, !frameInfo[i].fFullyReceived);
scroggo19b91532016-10-24 09:03:26 -0700259
260 SkBitmap frame;
261 frame.allocPixels(info);
262
263 SkCodec::Options opts;
264 opts.fFrameIndex = i;
265 SkCodec::Result result = partialCodec->startIncrementalDecode(info,
266 frame.getPixels(), frame.rowBytes(), &opts);
267 if (result != SkCodec::kSuccess) {
268 ERRORF(r, "Failed to start incremental decode for %s on frame %i",
269 path, i);
270 return;
271 }
272
273 result = partialCodec->incrementalDecode();
274 REPORTER_ASSERT(r, SkCodec::kIncompleteInput == result);
275
276 haltingStream->addNewData(secondHalf);
277 result = partialCodec->incrementalDecode();
278 REPORTER_ASSERT(r, SkCodec::kSuccess == result);
279
Leon Scroggins III3639faa2016-12-08 11:38:58 -0500280 frameInfo = partialCodec->getFrameInfo();
281 REPORTER_ASSERT(r, frameInfo.size() == i + 1);
282 REPORTER_ASSERT(r, frameInfo[i].fFullyReceived);
scroggo19b91532016-10-24 09:03:26 -0700283 compare_bitmaps(r, frames[i], frame);
284 }
scroggo8e6c7ad2016-09-16 08:20:38 -0700285}
286
287// Test that calling getPixels when an incremental decode has been
288// started (but not finished) makes the next call to incrementalDecode
289// require a call to startIncrementalDecode.
290static void test_interleaved(skiatest::Reporter* r, const char* name) {
Leon Scroggins IIIe4ba1052017-01-30 13:55:14 -0500291 sk_sp<SkData> file = GetResourceAsData(name);
scroggo19b91532016-10-24 09:03:26 -0700292 if (!file) {
293 return;
294 }
295 const size_t halfSize = file->size() / 2;
Ben Wagner145dbcd2016-11-03 14:40:50 -0400296 std::unique_ptr<SkCodec> partialCodec(SkCodec::NewFromStream(
scroggo19b91532016-10-24 09:03:26 -0700297 new HaltingStream(std::move(file), halfSize)));
scroggo8e6c7ad2016-09-16 08:20:38 -0700298 if (!partialCodec) {
299 ERRORF(r, "Failed to create codec for %s", name);
300 return;
301 }
302
Ben Wagner145dbcd2016-11-03 14:40:50 -0400303 const SkImageInfo info = standardize_info(partialCodec.get());
scroggo8e6c7ad2016-09-16 08:20:38 -0700304 SkBitmap incremental;
305 incremental.allocPixels(info);
306
307 const SkCodec::Result startResult = partialCodec->startIncrementalDecode(info,
308 incremental.getPixels(), incremental.rowBytes());
309 if (startResult != SkCodec::kSuccess) {
310 ERRORF(r, "Failed to start incremental decode\n");
311 return;
312 }
313
314 SkCodec::Result result = partialCodec->incrementalDecode();
315 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
316
317 SkBitmap full;
318 full.allocPixels(info);
319 result = partialCodec->getPixels(info, full.getPixels(), full.rowBytes());
320 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
321
322 // Now incremental decode will fail
323 result = partialCodec->incrementalDecode();
324 REPORTER_ASSERT(r, result == SkCodec::kInvalidParameters);
325}
326
327DEF_TEST(Codec_rewind, r) {
328 test_interleaved(r, "plane.png");
329 test_interleaved(r, "plane_interlaced.png");
scroggo19b91532016-10-24 09:03:26 -0700330 test_interleaved(r, "box.gif");
scroggo8e6c7ad2016-09-16 08:20:38 -0700331}
Leon Scroggins III3fc97d72016-12-09 16:39:33 -0500332
333// Modified version of the giflib logo, from
334// http://giflib.sourceforge.net/whatsinagif/bits_and_bytes.html
335// The global color map has been replaced with a local color map.
336static unsigned char gNoGlobalColorMap[] = {
337 // Header
338 0x47, 0x49, 0x46, 0x38, 0x39, 0x61,
339
340 // Logical screen descriptor
341 0x0A, 0x00, 0x0A, 0x00, 0x11, 0x00, 0x00,
342
343 // Image descriptor
344 0x2C, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x81,
345
346 // Local color table
347 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00,
348
349 // Image data
350 0x02, 0x16, 0x8C, 0x2D, 0x99, 0x87, 0x2A, 0x1C, 0xDC, 0x33, 0xA0, 0x02, 0x75,
351 0xEC, 0x95, 0xFA, 0xA8, 0xDE, 0x60, 0x8C, 0x04, 0x91, 0x4C, 0x01, 0x00,
352
353 // Trailer
354 0x3B,
355};
356
357// Test that a gif file truncated before its local color map behaves as expected.
358DEF_TEST(Codec_GifPreMap, r) {
359 sk_sp<SkData> data = SkData::MakeWithoutCopy(gNoGlobalColorMap, sizeof(gNoGlobalColorMap));
360 std::unique_ptr<SkCodec> codec(SkCodec::NewFromData(data));
361 if (!codec) {
362 ERRORF(r, "failed to create codec");
363 return;
364 }
365
366 SkBitmap truth;
367 auto info = standardize_info(codec.get());
368 truth.allocPixels(info);
369
370 auto result = codec->getPixels(info, truth.getPixels(), truth.rowBytes());
371 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
372
373 // Truncate to 23 bytes, just before the color map. This should fail to decode.
374 codec.reset(SkCodec::NewFromData(SkData::MakeWithoutCopy(gNoGlobalColorMap, 23)));
375 REPORTER_ASSERT(r, codec);
376 if (codec) {
377 SkBitmap bm;
378 bm.allocPixels(info);
379 result = codec->getPixels(info, bm.getPixels(), bm.rowBytes());
380 REPORTER_ASSERT(r, result == SkCodec::kInvalidInput);
381 }
382
383 // Again, truncate to 23 bytes, this time for an incremental decode. We
384 // cannot start an incremental decode until we have more data. If we did,
385 // we would be using the wrong color table.
386 HaltingStream* stream = new HaltingStream(data, 23);
387 codec.reset(SkCodec::NewFromStream(stream));
388 REPORTER_ASSERT(r, codec);
389 if (codec) {
390 SkBitmap bm;
391 bm.allocPixels(info);
392 result = codec->startIncrementalDecode(info, bm.getPixels(), bm.rowBytes());
393 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
394
395 stream->addNewData(data->size());
396 result = codec->startIncrementalDecode(info, bm.getPixels(), bm.rowBytes());
397 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
398
399 result = codec->incrementalDecode();
400 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
401 compare_bitmaps(r, truth, bm);
402 }
403}