blob: 93e5d63f2283e00a54f12f431b07af8d787d9888 [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
15#include "Resources.h"
16#include "Test.h"
17
18static sk_sp<SkData> make_from_resource(const char* name) {
19 SkString fullPath = GetResourcePath(name);
20 return SkData::MakeFromFileName(fullPath.c_str());
21}
22
23static SkImageInfo standardize_info(SkCodec* codec) {
24 SkImageInfo defaultInfo = codec->getInfo();
25 // Note: This drops the SkColorSpace, allowing the equality check between two
26 // different codecs created from the same file to have the same SkImageInfo.
27 return SkImageInfo::MakeN32Premul(defaultInfo.width(), defaultInfo.height());
28}
29
30static bool create_truth(sk_sp<SkData> data, SkBitmap* dst) {
Ben Wagner145dbcd2016-11-03 14:40:50 -040031 std::unique_ptr<SkCodec> codec(SkCodec::NewFromData(std::move(data)));
scroggo8e6c7ad2016-09-16 08:20:38 -070032 if (!codec) {
33 return false;
34 }
35
Ben Wagner145dbcd2016-11-03 14:40:50 -040036 const SkImageInfo info = standardize_info(codec.get());
scroggo8e6c7ad2016-09-16 08:20:38 -070037 dst->allocPixels(info);
38 return SkCodec::kSuccess == codec->getPixels(info, dst->getPixels(), dst->rowBytes());
39}
40
scroggo19b91532016-10-24 09:03:26 -070041static void compare_bitmaps(skiatest::Reporter* r, const SkBitmap& bm1, const SkBitmap& bm2) {
42 const SkImageInfo& info = bm1.info();
43 if (info != bm2.info()) {
44 ERRORF(r, "Bitmaps have different image infos!");
45 return;
46 }
47 const size_t rowBytes = info.minRowBytes();
48 for (int i = 0; i < info.height(); i++) {
49 REPORTER_ASSERT(r, !memcmp(bm1.getAddr(0, 0), bm2.getAddr(0, 0), rowBytes));
50 }
51}
52
scroggo8e6c7ad2016-09-16 08:20:38 -070053/*
54 * Represents a stream without all of its data.
55 */
56class HaltingStream : public SkStream {
57public:
scroggo19b91532016-10-24 09:03:26 -070058 HaltingStream(sk_sp<SkData> data, size_t initialLimit)
scroggo8e6c7ad2016-09-16 08:20:38 -070059 : fTotalSize(data->size())
scroggo19b91532016-10-24 09:03:26 -070060 , fLimit(initialLimit)
scroggo8e6c7ad2016-09-16 08:20:38 -070061 , fStream(std::move(data))
62 {}
63
scroggo19b91532016-10-24 09:03:26 -070064 void addNewData(size_t extra) {
65 fLimit = SkTMin(fTotalSize, fLimit + extra);
scroggo8e6c7ad2016-09-16 08:20:38 -070066 }
67
68 size_t read(void* buffer, size_t size) override {
69 if (fStream.getPosition() + size > fLimit) {
70 size = fLimit - fStream.getPosition();
71 }
72
73 return fStream.read(buffer, size);
74 }
75
76 bool isAtEnd() const override {
77 return fStream.isAtEnd();
78 }
79
80 bool hasPosition() const override { return true; }
81 size_t getPosition() const override { return fStream.getPosition(); }
82 bool rewind() override { return fStream.rewind(); }
83 bool move(long offset) override { return fStream.move(offset); }
84
scroggo19b91532016-10-24 09:03:26 -070085 bool isAllDataReceived() const { return fLimit == fTotalSize; }
86
scroggo8e6c7ad2016-09-16 08:20:38 -070087private:
88 const size_t fTotalSize;
89 size_t fLimit;
90 SkMemoryStream fStream;
91};
92
Leon Scroggins III4993b952016-12-08 11:54:04 -050093static void test_partial(skiatest::Reporter* r, const char* name, size_t minBytes = 0) {
scroggo8e6c7ad2016-09-16 08:20:38 -070094 sk_sp<SkData> file = make_from_resource(name);
95 if (!file) {
96 SkDebugf("missing resource %s\n", name);
97 return;
98 }
99
100 SkBitmap truth;
101 if (!create_truth(file, &truth)) {
102 ERRORF(r, "Failed to decode %s\n", name);
103 return;
104 }
105
scroggo8e6c7ad2016-09-16 08:20:38 -0700106 // Now decode part of the file
Leon Scroggins III4993b952016-12-08 11:54:04 -0500107 HaltingStream* stream = new HaltingStream(file, SkTMax(file->size() / 2, minBytes));
scroggo8e6c7ad2016-09-16 08:20:38 -0700108
109 // Note that we cheat and hold on to a pointer to stream, though it is owned by
110 // partialCodec.
Ben Wagner145dbcd2016-11-03 14:40:50 -0400111 std::unique_ptr<SkCodec> partialCodec(SkCodec::NewFromStream(stream));
scroggo8e6c7ad2016-09-16 08:20:38 -0700112 if (!partialCodec) {
113 // Technically, this could be a small file where half the file is not
114 // enough.
115 ERRORF(r, "Failed to create codec for %s", name);
116 return;
117 }
118
Ben Wagner145dbcd2016-11-03 14:40:50 -0400119 const SkImageInfo info = standardize_info(partialCodec.get());
scroggo8e6c7ad2016-09-16 08:20:38 -0700120 SkASSERT(info == truth.info());
121 SkBitmap incremental;
122 incremental.allocPixels(info);
123
scroggo19b91532016-10-24 09:03:26 -0700124 while (true) {
125 const SkCodec::Result startResult = partialCodec->startIncrementalDecode(info,
126 incremental.getPixels(), incremental.rowBytes());
127 if (startResult == SkCodec::kSuccess) {
128 break;
129 }
130
131 if (stream->isAllDataReceived()) {
132 ERRORF(r, "Failed to start incremental decode\n");
133 return;
134 }
135
136 // Append some data. The size is arbitrary, but deliberately different from
137 // the buffer size used by SkPngCodec.
138 stream->addNewData(1000);
scroggo8e6c7ad2016-09-16 08:20:38 -0700139 }
140
141 while (true) {
142 const SkCodec::Result result = partialCodec->incrementalDecode();
143
scroggo19b91532016-10-24 09:03:26 -0700144 if (result == SkCodec::kSuccess) {
scroggo8e6c7ad2016-09-16 08:20:38 -0700145 break;
146 }
147
scroggo8e6c7ad2016-09-16 08:20:38 -0700148 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
149
scroggo19b91532016-10-24 09:03:26 -0700150 if (stream->isAllDataReceived()) {
151 ERRORF(r, "Failed to completely decode %s", name);
152 return;
153 }
154
155 // Append some data. The size is arbitrary, but deliberately different from
156 // the buffer size used by SkPngCodec.
157 stream->addNewData(1000);
scroggo8e6c7ad2016-09-16 08:20:38 -0700158 }
159
160 // compare to original
scroggo19b91532016-10-24 09:03:26 -0700161 compare_bitmaps(r, truth, incremental);
scroggo8e6c7ad2016-09-16 08:20:38 -0700162}
163
164DEF_TEST(Codec_partial, r) {
165 test_partial(r, "plane.png");
166 test_partial(r, "plane_interlaced.png");
167 test_partial(r, "yellow_rose.png");
168 test_partial(r, "index8.png");
169 test_partial(r, "color_wheel.png");
170 test_partial(r, "mandrill_256.png");
171 test_partial(r, "mandrill_32.png");
172 test_partial(r, "arrow.png");
173 test_partial(r, "randPixels.png");
174 test_partial(r, "baby_tux.png");
scroggo19b91532016-10-24 09:03:26 -0700175
176 test_partial(r, "box.gif");
Leon Scroggins III4993b952016-12-08 11:54:04 -0500177 test_partial(r, "randPixels.gif", 215);
scroggo19b91532016-10-24 09:03:26 -0700178 test_partial(r, "color_wheel.gif");
179}
180
181DEF_TEST(Codec_partialAnim, r) {
182 auto path = "test640x479.gif";
183 sk_sp<SkData> file = make_from_resource(path);
184 if (!file) {
185 return;
186 }
187
188 // This stream will be owned by fullCodec, but we hang on to the pointer
189 // to determine frame offsets.
190 SkStream* stream = new SkMemoryStream(file);
191 std::unique_ptr<SkCodec> fullCodec(SkCodec::NewFromStream(stream));
192 const auto info = standardize_info(fullCodec.get());
193
194 // frameByteCounts stores the number of bytes to decode a particular frame.
195 // - [0] is the number of bytes for the header
196 // - frames[i] requires frameByteCounts[i+1] bytes to decode
197 std::vector<size_t> frameByteCounts;
198 std::vector<SkBitmap> frames;
199 size_t lastOffset = 0;
200 for (size_t i = 0; true; i++) {
201 frameByteCounts.push_back(stream->getPosition() - lastOffset);
202 lastOffset = stream->getPosition();
203
204 SkBitmap frame;
205 frame.allocPixels(info);
206
207 SkCodec::Options opts;
208 opts.fFrameIndex = i;
209 const SkCodec::Result result = fullCodec->getPixels(info, frame.getPixels(),
210 frame.rowBytes(), &opts, nullptr, nullptr);
211
Leon Scroggins III3fc97d72016-12-09 16:39:33 -0500212 if (result == SkCodec::kIncompleteInput || result == SkCodec::kInvalidInput) {
scroggo19b91532016-10-24 09:03:26 -0700213 frameByteCounts.push_back(stream->getPosition() - lastOffset);
214
215 // 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);
277
scroggo19b91532016-10-24 09:03:26 -0700278 // allocPixels locked the pixels for frame, but frames[i] was copied
279 // from another bitmap, and did not retain the locked status.
280 SkAutoLockPixels alp(frames[i]);
281 compare_bitmaps(r, frames[i], frame);
282 }
scroggo8e6c7ad2016-09-16 08:20:38 -0700283}
284
285// Test that calling getPixels when an incremental decode has been
286// started (but not finished) makes the next call to incrementalDecode
287// require a call to startIncrementalDecode.
288static void test_interleaved(skiatest::Reporter* r, const char* name) {
289 sk_sp<SkData> file = make_from_resource(name);
scroggo19b91532016-10-24 09:03:26 -0700290 if (!file) {
291 return;
292 }
293 const size_t halfSize = file->size() / 2;
Ben Wagner145dbcd2016-11-03 14:40:50 -0400294 std::unique_ptr<SkCodec> partialCodec(SkCodec::NewFromStream(
scroggo19b91532016-10-24 09:03:26 -0700295 new HaltingStream(std::move(file), halfSize)));
scroggo8e6c7ad2016-09-16 08:20:38 -0700296 if (!partialCodec) {
297 ERRORF(r, "Failed to create codec for %s", name);
298 return;
299 }
300
Ben Wagner145dbcd2016-11-03 14:40:50 -0400301 const SkImageInfo info = standardize_info(partialCodec.get());
scroggo8e6c7ad2016-09-16 08:20:38 -0700302 SkBitmap incremental;
303 incremental.allocPixels(info);
304
305 const SkCodec::Result startResult = partialCodec->startIncrementalDecode(info,
306 incremental.getPixels(), incremental.rowBytes());
307 if (startResult != SkCodec::kSuccess) {
308 ERRORF(r, "Failed to start incremental decode\n");
309 return;
310 }
311
312 SkCodec::Result result = partialCodec->incrementalDecode();
313 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
314
315 SkBitmap full;
316 full.allocPixels(info);
317 result = partialCodec->getPixels(info, full.getPixels(), full.rowBytes());
318 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
319
320 // Now incremental decode will fail
321 result = partialCodec->incrementalDecode();
322 REPORTER_ASSERT(r, result == SkCodec::kInvalidParameters);
323}
324
325DEF_TEST(Codec_rewind, r) {
326 test_interleaved(r, "plane.png");
327 test_interleaved(r, "plane_interlaced.png");
scroggo19b91532016-10-24 09:03:26 -0700328 test_interleaved(r, "box.gif");
scroggo8e6c7ad2016-09-16 08:20:38 -0700329}
Leon Scroggins III3fc97d72016-12-09 16:39:33 -0500330
331// Modified version of the giflib logo, from
332// http://giflib.sourceforge.net/whatsinagif/bits_and_bytes.html
333// The global color map has been replaced with a local color map.
334static unsigned char gNoGlobalColorMap[] = {
335 // Header
336 0x47, 0x49, 0x46, 0x38, 0x39, 0x61,
337
338 // Logical screen descriptor
339 0x0A, 0x00, 0x0A, 0x00, 0x11, 0x00, 0x00,
340
341 // Image descriptor
342 0x2C, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x81,
343
344 // Local color table
345 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00,
346
347 // Image data
348 0x02, 0x16, 0x8C, 0x2D, 0x99, 0x87, 0x2A, 0x1C, 0xDC, 0x33, 0xA0, 0x02, 0x75,
349 0xEC, 0x95, 0xFA, 0xA8, 0xDE, 0x60, 0x8C, 0x04, 0x91, 0x4C, 0x01, 0x00,
350
351 // Trailer
352 0x3B,
353};
354
355// Test that a gif file truncated before its local color map behaves as expected.
356DEF_TEST(Codec_GifPreMap, r) {
357 sk_sp<SkData> data = SkData::MakeWithoutCopy(gNoGlobalColorMap, sizeof(gNoGlobalColorMap));
358 std::unique_ptr<SkCodec> codec(SkCodec::NewFromData(data));
359 if (!codec) {
360 ERRORF(r, "failed to create codec");
361 return;
362 }
363
364 SkBitmap truth;
365 auto info = standardize_info(codec.get());
366 truth.allocPixels(info);
367
368 auto result = codec->getPixels(info, truth.getPixels(), truth.rowBytes());
369 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
370
371 // Truncate to 23 bytes, just before the color map. This should fail to decode.
372 codec.reset(SkCodec::NewFromData(SkData::MakeWithoutCopy(gNoGlobalColorMap, 23)));
373 REPORTER_ASSERT(r, codec);
374 if (codec) {
375 SkBitmap bm;
376 bm.allocPixels(info);
377 result = codec->getPixels(info, bm.getPixels(), bm.rowBytes());
378 REPORTER_ASSERT(r, result == SkCodec::kInvalidInput);
379 }
380
381 // Again, truncate to 23 bytes, this time for an incremental decode. We
382 // cannot start an incremental decode until we have more data. If we did,
383 // we would be using the wrong color table.
384 HaltingStream* stream = new HaltingStream(data, 23);
385 codec.reset(SkCodec::NewFromStream(stream));
386 REPORTER_ASSERT(r, codec);
387 if (codec) {
388 SkBitmap bm;
389 bm.allocPixels(info);
390 result = codec->startIncrementalDecode(info, bm.getPixels(), bm.rowBytes());
391 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
392
393 stream->addNewData(data->size());
394 result = codec->startIncrementalDecode(info, bm.getPixels(), bm.rowBytes());
395 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
396
397 result = codec->incrementalDecode();
398 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
399 compare_bitmaps(r, truth, bm);
400 }
401}