blob: f5eea371d9cb75d30325db356bb34aa63a8f5eb9 [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) {
121 test_partial(r, "plane.png");
122 test_partial(r, "plane_interlaced.png");
123 test_partial(r, "yellow_rose.png");
124 test_partial(r, "index8.png");
125 test_partial(r, "color_wheel.png");
126 test_partial(r, "mandrill_256.png");
127 test_partial(r, "mandrill_32.png");
128 test_partial(r, "arrow.png");
129 test_partial(r, "randPixels.png");
130 test_partial(r, "baby_tux.png");
scroggo19b91532016-10-24 09:03:26 -0700131
132 test_partial(r, "box.gif");
Leon Scroggins III4993b952016-12-08 11:54:04 -0500133 test_partial(r, "randPixels.gif", 215);
scroggo19b91532016-10-24 09:03:26 -0700134 test_partial(r, "color_wheel.gif");
135}
136
Leon Scroggins IIIe4ba1052017-01-30 13:55:14 -0500137// Verify that when decoding an animated gif byte by byte we report the correct
138// fRequiredFrame as soon as getFrameInfo reports the frame.
139DEF_TEST(Codec_requiredFrame, r) {
140 auto path = "colorTables.gif";
141 sk_sp<SkData> file = GetResourceAsData(path);
142 if (!file) {
143 return;
144 }
145
146 std::unique_ptr<SkCodec> codec(SkCodec::NewFromData(file));
147 if (!codec) {
148 ERRORF(r, "Failed to create codec from %s", path);
149 return;
150 }
151
152 auto frameInfo = codec->getFrameInfo();
153 if (frameInfo.size() <= 1) {
154 ERRORF(r, "Test is uninteresting with 0 or 1 frames");
155 return;
156 }
157
158 HaltingStream* stream(nullptr);
159 std::unique_ptr<SkCodec> partialCodec(nullptr);
160 for (size_t i = 0; !partialCodec; i++) {
161 if (file->size() == i) {
162 ERRORF(r, "Should have created a partial codec for %s", path);
163 return;
164 }
165 stream = new HaltingStream(file, i);
166 partialCodec.reset(SkCodec::NewFromStream(stream));
167 }
168
169 std::vector<SkCodec::FrameInfo> partialInfo;
170 size_t frameToCompare = 0;
171 for (; stream->getLength() <= file->size(); stream->addNewData(1)) {
172 partialInfo = partialCodec->getFrameInfo();
173 for (; frameToCompare < partialInfo.size(); frameToCompare++) {
174 REPORTER_ASSERT(r, partialInfo[frameToCompare].fRequiredFrame
175 == frameInfo[frameToCompare].fRequiredFrame);
176 }
177
178 if (frameToCompare == frameInfo.size()) {
179 break;
180 }
181 }
182}
183
scroggo19b91532016-10-24 09:03:26 -0700184DEF_TEST(Codec_partialAnim, r) {
185 auto path = "test640x479.gif";
Leon Scroggins IIIe4ba1052017-01-30 13:55:14 -0500186 sk_sp<SkData> file = GetResourceAsData(path);
scroggo19b91532016-10-24 09:03:26 -0700187 if (!file) {
188 return;
189 }
190
191 // This stream will be owned by fullCodec, but we hang on to the pointer
192 // to determine frame offsets.
193 SkStream* stream = new SkMemoryStream(file);
194 std::unique_ptr<SkCodec> fullCodec(SkCodec::NewFromStream(stream));
195 const auto info = standardize_info(fullCodec.get());
196
197 // frameByteCounts stores the number of bytes to decode a particular frame.
198 // - [0] is the number of bytes for the header
199 // - frames[i] requires frameByteCounts[i+1] bytes to decode
200 std::vector<size_t> frameByteCounts;
201 std::vector<SkBitmap> frames;
202 size_t lastOffset = 0;
203 for (size_t i = 0; true; i++) {
204 frameByteCounts.push_back(stream->getPosition() - lastOffset);
205 lastOffset = stream->getPosition();
206
207 SkBitmap frame;
208 frame.allocPixels(info);
209
210 SkCodec::Options opts;
211 opts.fFrameIndex = i;
212 const SkCodec::Result result = fullCodec->getPixels(info, frame.getPixels(),
213 frame.rowBytes(), &opts, nullptr, nullptr);
214
Leon Scroggins III3fc97d72016-12-09 16:39:33 -0500215 if (result == SkCodec::kIncompleteInput || result == SkCodec::kInvalidInput) {
scroggo19b91532016-10-24 09:03:26 -0700216 frameByteCounts.push_back(stream->getPosition() - lastOffset);
217
218 // 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]);
239 std::unique_ptr<SkCodec> partialCodec(SkCodec::NewFromStream(haltingStream));
240 if (!partialCodec) {
241 ERRORF(r, "Failed to create a partial codec from %s with %i bytes out of %i",
242 path, frameByteCounts[0], file->size());
243 return;
244 }
245
246 SkASSERT(frameByteCounts.size() > frames.size());
247 for (size_t i = 0; i < frames.size(); i++) {
248 const size_t fullFrameBytes = frameByteCounts[i + 1];
249 const size_t firstHalf = fullFrameBytes / 2;
250 const size_t secondHalf = fullFrameBytes - firstHalf;
251
252 haltingStream->addNewData(firstHalf);
Leon Scroggins III3639faa2016-12-08 11:38:58 -0500253 auto frameInfo = partialCodec->getFrameInfo();
254 REPORTER_ASSERT(r, frameInfo.size() == i + 1);
255 REPORTER_ASSERT(r, !frameInfo[i].fFullyReceived);
scroggo19b91532016-10-24 09:03:26 -0700256
257 SkBitmap frame;
258 frame.allocPixels(info);
259
260 SkCodec::Options opts;
261 opts.fFrameIndex = i;
262 SkCodec::Result result = partialCodec->startIncrementalDecode(info,
263 frame.getPixels(), frame.rowBytes(), &opts);
264 if (result != SkCodec::kSuccess) {
265 ERRORF(r, "Failed to start incremental decode for %s on frame %i",
266 path, i);
267 return;
268 }
269
270 result = partialCodec->incrementalDecode();
271 REPORTER_ASSERT(r, SkCodec::kIncompleteInput == result);
272
273 haltingStream->addNewData(secondHalf);
274 result = partialCodec->incrementalDecode();
275 REPORTER_ASSERT(r, SkCodec::kSuccess == result);
276
Leon Scroggins III3639faa2016-12-08 11:38:58 -0500277 frameInfo = partialCodec->getFrameInfo();
278 REPORTER_ASSERT(r, frameInfo.size() == i + 1);
279 REPORTER_ASSERT(r, frameInfo[i].fFullyReceived);
280
scroggo19b91532016-10-24 09:03:26 -0700281 // allocPixels locked the pixels for frame, but frames[i] was copied
282 // from another bitmap, and did not retain the locked status.
283 SkAutoLockPixels alp(frames[i]);
284 compare_bitmaps(r, frames[i], frame);
285 }
scroggo8e6c7ad2016-09-16 08:20:38 -0700286}
287
288// Test that calling getPixels when an incremental decode has been
289// started (but not finished) makes the next call to incrementalDecode
290// require a call to startIncrementalDecode.
291static void test_interleaved(skiatest::Reporter* r, const char* name) {
Leon Scroggins IIIe4ba1052017-01-30 13:55:14 -0500292 sk_sp<SkData> file = GetResourceAsData(name);
scroggo19b91532016-10-24 09:03:26 -0700293 if (!file) {
294 return;
295 }
296 const size_t halfSize = file->size() / 2;
Ben Wagner145dbcd2016-11-03 14:40:50 -0400297 std::unique_ptr<SkCodec> partialCodec(SkCodec::NewFromStream(
scroggo19b91532016-10-24 09:03:26 -0700298 new HaltingStream(std::move(file), halfSize)));
scroggo8e6c7ad2016-09-16 08:20:38 -0700299 if (!partialCodec) {
300 ERRORF(r, "Failed to create codec for %s", name);
301 return;
302 }
303
Ben Wagner145dbcd2016-11-03 14:40:50 -0400304 const SkImageInfo info = standardize_info(partialCodec.get());
scroggo8e6c7ad2016-09-16 08:20:38 -0700305 SkBitmap incremental;
306 incremental.allocPixels(info);
307
308 const SkCodec::Result startResult = partialCodec->startIncrementalDecode(info,
309 incremental.getPixels(), incremental.rowBytes());
310 if (startResult != SkCodec::kSuccess) {
311 ERRORF(r, "Failed to start incremental decode\n");
312 return;
313 }
314
315 SkCodec::Result result = partialCodec->incrementalDecode();
316 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
317
318 SkBitmap full;
319 full.allocPixels(info);
320 result = partialCodec->getPixels(info, full.getPixels(), full.rowBytes());
321 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
322
323 // Now incremental decode will fail
324 result = partialCodec->incrementalDecode();
325 REPORTER_ASSERT(r, result == SkCodec::kInvalidParameters);
326}
327
328DEF_TEST(Codec_rewind, r) {
329 test_interleaved(r, "plane.png");
330 test_interleaved(r, "plane_interlaced.png");
scroggo19b91532016-10-24 09:03:26 -0700331 test_interleaved(r, "box.gif");
scroggo8e6c7ad2016-09-16 08:20:38 -0700332}
Leon Scroggins III3fc97d72016-12-09 16:39:33 -0500333
334// Modified version of the giflib logo, from
335// http://giflib.sourceforge.net/whatsinagif/bits_and_bytes.html
336// The global color map has been replaced with a local color map.
337static unsigned char gNoGlobalColorMap[] = {
338 // Header
339 0x47, 0x49, 0x46, 0x38, 0x39, 0x61,
340
341 // Logical screen descriptor
342 0x0A, 0x00, 0x0A, 0x00, 0x11, 0x00, 0x00,
343
344 // Image descriptor
345 0x2C, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x81,
346
347 // Local color table
348 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00,
349
350 // Image data
351 0x02, 0x16, 0x8C, 0x2D, 0x99, 0x87, 0x2A, 0x1C, 0xDC, 0x33, 0xA0, 0x02, 0x75,
352 0xEC, 0x95, 0xFA, 0xA8, 0xDE, 0x60, 0x8C, 0x04, 0x91, 0x4C, 0x01, 0x00,
353
354 // Trailer
355 0x3B,
356};
357
358// Test that a gif file truncated before its local color map behaves as expected.
359DEF_TEST(Codec_GifPreMap, r) {
360 sk_sp<SkData> data = SkData::MakeWithoutCopy(gNoGlobalColorMap, sizeof(gNoGlobalColorMap));
361 std::unique_ptr<SkCodec> codec(SkCodec::NewFromData(data));
362 if (!codec) {
363 ERRORF(r, "failed to create codec");
364 return;
365 }
366
367 SkBitmap truth;
368 auto info = standardize_info(codec.get());
369 truth.allocPixels(info);
370
371 auto result = codec->getPixels(info, truth.getPixels(), truth.rowBytes());
372 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
373
374 // Truncate to 23 bytes, just before the color map. This should fail to decode.
375 codec.reset(SkCodec::NewFromData(SkData::MakeWithoutCopy(gNoGlobalColorMap, 23)));
376 REPORTER_ASSERT(r, codec);
377 if (codec) {
378 SkBitmap bm;
379 bm.allocPixels(info);
380 result = codec->getPixels(info, bm.getPixels(), bm.rowBytes());
381 REPORTER_ASSERT(r, result == SkCodec::kInvalidInput);
382 }
383
384 // Again, truncate to 23 bytes, this time for an incremental decode. We
385 // cannot start an incremental decode until we have more data. If we did,
386 // we would be using the wrong color table.
387 HaltingStream* stream = new HaltingStream(data, 23);
388 codec.reset(SkCodec::NewFromStream(stream));
389 REPORTER_ASSERT(r, codec);
390 if (codec) {
391 SkBitmap bm;
392 bm.allocPixels(info);
393 result = codec->startIncrementalDecode(info, bm.getPixels(), bm.rowBytes());
394 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
395
396 stream->addNewData(data->size());
397 result = codec->startIncrementalDecode(info, bm.getPixels(), bm.rowBytes());
398 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
399
400 result = codec->incrementalDecode();
401 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
402 compare_bitmaps(r, truth, bm);
403 }
404}