blob: 62f9d8570ef2167f61ee9c9714a23612fc709718 [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
19static sk_sp<SkData> make_from_resource(const char* name) {
20 SkString fullPath = GetResourcePath(name);
21 return SkData::MakeFromFileName(fullPath.c_str());
22}
23
24static SkImageInfo standardize_info(SkCodec* codec) {
25 SkImageInfo defaultInfo = codec->getInfo();
26 // Note: This drops the SkColorSpace, allowing the equality check between two
27 // different codecs created from the same file to have the same SkImageInfo.
28 return SkImageInfo::MakeN32Premul(defaultInfo.width(), defaultInfo.height());
29}
30
31static bool create_truth(sk_sp<SkData> data, SkBitmap* dst) {
Ben Wagner145dbcd2016-11-03 14:40:50 -040032 std::unique_ptr<SkCodec> codec(SkCodec::NewFromData(std::move(data)));
scroggo8e6c7ad2016-09-16 08:20:38 -070033 if (!codec) {
34 return false;
35 }
36
Ben Wagner145dbcd2016-11-03 14:40:50 -040037 const SkImageInfo info = standardize_info(codec.get());
scroggo8e6c7ad2016-09-16 08:20:38 -070038 dst->allocPixels(info);
39 return SkCodec::kSuccess == codec->getPixels(info, dst->getPixels(), dst->rowBytes());
40}
41
scroggo19b91532016-10-24 09:03:26 -070042static void compare_bitmaps(skiatest::Reporter* r, const SkBitmap& bm1, const SkBitmap& bm2) {
43 const SkImageInfo& info = bm1.info();
44 if (info != bm2.info()) {
45 ERRORF(r, "Bitmaps have different image infos!");
46 return;
47 }
48 const size_t rowBytes = info.minRowBytes();
49 for (int i = 0; i < info.height(); i++) {
50 REPORTER_ASSERT(r, !memcmp(bm1.getAddr(0, 0), bm2.getAddr(0, 0), rowBytes));
51 }
52}
53
Leon Scroggins III4993b952016-12-08 11:54:04 -050054static void test_partial(skiatest::Reporter* r, const char* name, size_t minBytes = 0) {
scroggo8e6c7ad2016-09-16 08:20:38 -070055 sk_sp<SkData> file = make_from_resource(name);
56 if (!file) {
57 SkDebugf("missing resource %s\n", name);
58 return;
59 }
60
61 SkBitmap truth;
62 if (!create_truth(file, &truth)) {
63 ERRORF(r, "Failed to decode %s\n", name);
64 return;
65 }
66
scroggo8e6c7ad2016-09-16 08:20:38 -070067 // Now decode part of the file
Leon Scroggins III4993b952016-12-08 11:54:04 -050068 HaltingStream* stream = new HaltingStream(file, SkTMax(file->size() / 2, minBytes));
scroggo8e6c7ad2016-09-16 08:20:38 -070069
70 // Note that we cheat and hold on to a pointer to stream, though it is owned by
71 // partialCodec.
Ben Wagner145dbcd2016-11-03 14:40:50 -040072 std::unique_ptr<SkCodec> partialCodec(SkCodec::NewFromStream(stream));
scroggo8e6c7ad2016-09-16 08:20:38 -070073 if (!partialCodec) {
74 // Technically, this could be a small file where half the file is not
75 // enough.
76 ERRORF(r, "Failed to create codec for %s", name);
77 return;
78 }
79
Ben Wagner145dbcd2016-11-03 14:40:50 -040080 const SkImageInfo info = standardize_info(partialCodec.get());
scroggo8e6c7ad2016-09-16 08:20:38 -070081 SkASSERT(info == truth.info());
82 SkBitmap incremental;
83 incremental.allocPixels(info);
84
scroggo19b91532016-10-24 09:03:26 -070085 while (true) {
86 const SkCodec::Result startResult = partialCodec->startIncrementalDecode(info,
87 incremental.getPixels(), incremental.rowBytes());
88 if (startResult == SkCodec::kSuccess) {
89 break;
90 }
91
92 if (stream->isAllDataReceived()) {
93 ERRORF(r, "Failed to start incremental decode\n");
94 return;
95 }
96
97 // Append some data. The size is arbitrary, but deliberately different from
98 // the buffer size used by SkPngCodec.
99 stream->addNewData(1000);
scroggo8e6c7ad2016-09-16 08:20:38 -0700100 }
101
102 while (true) {
103 const SkCodec::Result result = partialCodec->incrementalDecode();
104
scroggo19b91532016-10-24 09:03:26 -0700105 if (result == SkCodec::kSuccess) {
scroggo8e6c7ad2016-09-16 08:20:38 -0700106 break;
107 }
108
scroggo8e6c7ad2016-09-16 08:20:38 -0700109 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
110
scroggo19b91532016-10-24 09:03:26 -0700111 if (stream->isAllDataReceived()) {
112 ERRORF(r, "Failed to completely decode %s", name);
113 return;
114 }
115
116 // Append some data. The size is arbitrary, but deliberately different from
117 // the buffer size used by SkPngCodec.
118 stream->addNewData(1000);
scroggo8e6c7ad2016-09-16 08:20:38 -0700119 }
120
121 // compare to original
scroggo19b91532016-10-24 09:03:26 -0700122 compare_bitmaps(r, truth, incremental);
scroggo8e6c7ad2016-09-16 08:20:38 -0700123}
124
125DEF_TEST(Codec_partial, r) {
126 test_partial(r, "plane.png");
127 test_partial(r, "plane_interlaced.png");
128 test_partial(r, "yellow_rose.png");
129 test_partial(r, "index8.png");
130 test_partial(r, "color_wheel.png");
131 test_partial(r, "mandrill_256.png");
132 test_partial(r, "mandrill_32.png");
133 test_partial(r, "arrow.png");
134 test_partial(r, "randPixels.png");
135 test_partial(r, "baby_tux.png");
scroggo19b91532016-10-24 09:03:26 -0700136
137 test_partial(r, "box.gif");
Leon Scroggins III4993b952016-12-08 11:54:04 -0500138 test_partial(r, "randPixels.gif", 215);
scroggo19b91532016-10-24 09:03:26 -0700139 test_partial(r, "color_wheel.gif");
140}
141
142DEF_TEST(Codec_partialAnim, r) {
143 auto path = "test640x479.gif";
144 sk_sp<SkData> file = make_from_resource(path);
145 if (!file) {
146 return;
147 }
148
149 // This stream will be owned by fullCodec, but we hang on to the pointer
150 // to determine frame offsets.
151 SkStream* stream = new SkMemoryStream(file);
152 std::unique_ptr<SkCodec> fullCodec(SkCodec::NewFromStream(stream));
153 const auto info = standardize_info(fullCodec.get());
154
155 // frameByteCounts stores the number of bytes to decode a particular frame.
156 // - [0] is the number of bytes for the header
157 // - frames[i] requires frameByteCounts[i+1] bytes to decode
158 std::vector<size_t> frameByteCounts;
159 std::vector<SkBitmap> frames;
160 size_t lastOffset = 0;
161 for (size_t i = 0; true; i++) {
162 frameByteCounts.push_back(stream->getPosition() - lastOffset);
163 lastOffset = stream->getPosition();
164
165 SkBitmap frame;
166 frame.allocPixels(info);
167
168 SkCodec::Options opts;
169 opts.fFrameIndex = i;
170 const SkCodec::Result result = fullCodec->getPixels(info, frame.getPixels(),
171 frame.rowBytes(), &opts, nullptr, nullptr);
172
Leon Scroggins III3fc97d72016-12-09 16:39:33 -0500173 if (result == SkCodec::kIncompleteInput || result == SkCodec::kInvalidInput) {
scroggo19b91532016-10-24 09:03:26 -0700174 frameByteCounts.push_back(stream->getPosition() - lastOffset);
175
176 // We need to distinguish between a partial frame and no more frames.
177 // getFrameInfo lets us do this, since it tells the number of frames
178 // not considering whether they are complete.
179 // FIXME: Should we use a different Result?
180 if (fullCodec->getFrameInfo().size() > i) {
181 // This is a partial frame.
182 frames.push_back(frame);
183 }
184 break;
185 }
186
187 if (result != SkCodec::kSuccess) {
188 ERRORF(r, "Failed to decode frame %i from %s", i, path);
189 return;
190 }
191
192 frames.push_back(frame);
193 }
194
195 // Now decode frames partially, then completely, and compare to the original.
196 HaltingStream* haltingStream = new HaltingStream(file, frameByteCounts[0]);
197 std::unique_ptr<SkCodec> partialCodec(SkCodec::NewFromStream(haltingStream));
198 if (!partialCodec) {
199 ERRORF(r, "Failed to create a partial codec from %s with %i bytes out of %i",
200 path, frameByteCounts[0], file->size());
201 return;
202 }
203
204 SkASSERT(frameByteCounts.size() > frames.size());
205 for (size_t i = 0; i < frames.size(); i++) {
206 const size_t fullFrameBytes = frameByteCounts[i + 1];
207 const size_t firstHalf = fullFrameBytes / 2;
208 const size_t secondHalf = fullFrameBytes - firstHalf;
209
210 haltingStream->addNewData(firstHalf);
Leon Scroggins III3639faa2016-12-08 11:38:58 -0500211 auto frameInfo = partialCodec->getFrameInfo();
212 REPORTER_ASSERT(r, frameInfo.size() == i + 1);
213 REPORTER_ASSERT(r, !frameInfo[i].fFullyReceived);
scroggo19b91532016-10-24 09:03:26 -0700214
215 SkBitmap frame;
216 frame.allocPixels(info);
217
218 SkCodec::Options opts;
219 opts.fFrameIndex = i;
220 SkCodec::Result result = partialCodec->startIncrementalDecode(info,
221 frame.getPixels(), frame.rowBytes(), &opts);
222 if (result != SkCodec::kSuccess) {
223 ERRORF(r, "Failed to start incremental decode for %s on frame %i",
224 path, i);
225 return;
226 }
227
228 result = partialCodec->incrementalDecode();
229 REPORTER_ASSERT(r, SkCodec::kIncompleteInput == result);
230
231 haltingStream->addNewData(secondHalf);
232 result = partialCodec->incrementalDecode();
233 REPORTER_ASSERT(r, SkCodec::kSuccess == result);
234
Leon Scroggins III3639faa2016-12-08 11:38:58 -0500235 frameInfo = partialCodec->getFrameInfo();
236 REPORTER_ASSERT(r, frameInfo.size() == i + 1);
237 REPORTER_ASSERT(r, frameInfo[i].fFullyReceived);
238
scroggo19b91532016-10-24 09:03:26 -0700239 // allocPixels locked the pixels for frame, but frames[i] was copied
240 // from another bitmap, and did not retain the locked status.
241 SkAutoLockPixels alp(frames[i]);
242 compare_bitmaps(r, frames[i], frame);
243 }
scroggo8e6c7ad2016-09-16 08:20:38 -0700244}
245
246// Test that calling getPixels when an incremental decode has been
247// started (but not finished) makes the next call to incrementalDecode
248// require a call to startIncrementalDecode.
249static void test_interleaved(skiatest::Reporter* r, const char* name) {
250 sk_sp<SkData> file = make_from_resource(name);
scroggo19b91532016-10-24 09:03:26 -0700251 if (!file) {
252 return;
253 }
254 const size_t halfSize = file->size() / 2;
Ben Wagner145dbcd2016-11-03 14:40:50 -0400255 std::unique_ptr<SkCodec> partialCodec(SkCodec::NewFromStream(
scroggo19b91532016-10-24 09:03:26 -0700256 new HaltingStream(std::move(file), halfSize)));
scroggo8e6c7ad2016-09-16 08:20:38 -0700257 if (!partialCodec) {
258 ERRORF(r, "Failed to create codec for %s", name);
259 return;
260 }
261
Ben Wagner145dbcd2016-11-03 14:40:50 -0400262 const SkImageInfo info = standardize_info(partialCodec.get());
scroggo8e6c7ad2016-09-16 08:20:38 -0700263 SkBitmap incremental;
264 incremental.allocPixels(info);
265
266 const SkCodec::Result startResult = partialCodec->startIncrementalDecode(info,
267 incremental.getPixels(), incremental.rowBytes());
268 if (startResult != SkCodec::kSuccess) {
269 ERRORF(r, "Failed to start incremental decode\n");
270 return;
271 }
272
273 SkCodec::Result result = partialCodec->incrementalDecode();
274 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
275
276 SkBitmap full;
277 full.allocPixels(info);
278 result = partialCodec->getPixels(info, full.getPixels(), full.rowBytes());
279 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
280
281 // Now incremental decode will fail
282 result = partialCodec->incrementalDecode();
283 REPORTER_ASSERT(r, result == SkCodec::kInvalidParameters);
284}
285
286DEF_TEST(Codec_rewind, r) {
287 test_interleaved(r, "plane.png");
288 test_interleaved(r, "plane_interlaced.png");
scroggo19b91532016-10-24 09:03:26 -0700289 test_interleaved(r, "box.gif");
scroggo8e6c7ad2016-09-16 08:20:38 -0700290}
Leon Scroggins III3fc97d72016-12-09 16:39:33 -0500291
292// Modified version of the giflib logo, from
293// http://giflib.sourceforge.net/whatsinagif/bits_and_bytes.html
294// The global color map has been replaced with a local color map.
295static unsigned char gNoGlobalColorMap[] = {
296 // Header
297 0x47, 0x49, 0x46, 0x38, 0x39, 0x61,
298
299 // Logical screen descriptor
300 0x0A, 0x00, 0x0A, 0x00, 0x11, 0x00, 0x00,
301
302 // Image descriptor
303 0x2C, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x81,
304
305 // Local color table
306 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00,
307
308 // Image data
309 0x02, 0x16, 0x8C, 0x2D, 0x99, 0x87, 0x2A, 0x1C, 0xDC, 0x33, 0xA0, 0x02, 0x75,
310 0xEC, 0x95, 0xFA, 0xA8, 0xDE, 0x60, 0x8C, 0x04, 0x91, 0x4C, 0x01, 0x00,
311
312 // Trailer
313 0x3B,
314};
315
316// Test that a gif file truncated before its local color map behaves as expected.
317DEF_TEST(Codec_GifPreMap, r) {
318 sk_sp<SkData> data = SkData::MakeWithoutCopy(gNoGlobalColorMap, sizeof(gNoGlobalColorMap));
319 std::unique_ptr<SkCodec> codec(SkCodec::NewFromData(data));
320 if (!codec) {
321 ERRORF(r, "failed to create codec");
322 return;
323 }
324
325 SkBitmap truth;
326 auto info = standardize_info(codec.get());
327 truth.allocPixels(info);
328
329 auto result = codec->getPixels(info, truth.getPixels(), truth.rowBytes());
330 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
331
332 // Truncate to 23 bytes, just before the color map. This should fail to decode.
333 codec.reset(SkCodec::NewFromData(SkData::MakeWithoutCopy(gNoGlobalColorMap, 23)));
334 REPORTER_ASSERT(r, codec);
335 if (codec) {
336 SkBitmap bm;
337 bm.allocPixels(info);
338 result = codec->getPixels(info, bm.getPixels(), bm.rowBytes());
339 REPORTER_ASSERT(r, result == SkCodec::kInvalidInput);
340 }
341
342 // Again, truncate to 23 bytes, this time for an incremental decode. We
343 // cannot start an incremental decode until we have more data. If we did,
344 // we would be using the wrong color table.
345 HaltingStream* stream = new HaltingStream(data, 23);
346 codec.reset(SkCodec::NewFromStream(stream));
347 REPORTER_ASSERT(r, codec);
348 if (codec) {
349 SkBitmap bm;
350 bm.allocPixels(info);
351 result = codec->startIncrementalDecode(info, bm.getPixels(), bm.rowBytes());
352 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
353
354 stream->addNewData(data->size());
355 result = codec->startIncrementalDecode(info, bm.getPixels(), bm.rowBytes());
356 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
357
358 result = codec->incrementalDecode();
359 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
360 compare_bitmaps(r, truth, bm);
361 }
362}