blob: e29037dd149d9cfd7dd4952250817d5c109a40fc [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);
250
251 SkBitmap frame;
252 frame.allocPixels(info);
253
254 SkCodec::Options opts;
255 opts.fFrameIndex = i;
256 SkCodec::Result result = partialCodec->startIncrementalDecode(info,
257 frame.getPixels(), frame.rowBytes(), &opts);
258 if (result != SkCodec::kSuccess) {
259 ERRORF(r, "Failed to start incremental decode for %s on frame %i",
260 path, i);
261 return;
262 }
263
264 result = partialCodec->incrementalDecode();
265 REPORTER_ASSERT(r, SkCodec::kIncompleteInput == result);
266
267 haltingStream->addNewData(secondHalf);
268 result = partialCodec->incrementalDecode();
269 REPORTER_ASSERT(r, SkCodec::kSuccess == result);
270
271 // allocPixels locked the pixels for frame, but frames[i] was copied
272 // from another bitmap, and did not retain the locked status.
273 SkAutoLockPixels alp(frames[i]);
274 compare_bitmaps(r, frames[i], frame);
275 }
scroggo8e6c7ad2016-09-16 08:20:38 -0700276}
277
278// Test that calling getPixels when an incremental decode has been
279// started (but not finished) makes the next call to incrementalDecode
280// require a call to startIncrementalDecode.
281static void test_interleaved(skiatest::Reporter* r, const char* name) {
282 sk_sp<SkData> file = make_from_resource(name);
scroggo19b91532016-10-24 09:03:26 -0700283 if (!file) {
284 return;
285 }
286 const size_t halfSize = file->size() / 2;
Ben Wagner145dbcd2016-11-03 14:40:50 -0400287 std::unique_ptr<SkCodec> partialCodec(SkCodec::NewFromStream(
scroggo19b91532016-10-24 09:03:26 -0700288 new HaltingStream(std::move(file), halfSize)));
scroggo8e6c7ad2016-09-16 08:20:38 -0700289 if (!partialCodec) {
290 ERRORF(r, "Failed to create codec for %s", name);
291 return;
292 }
293
Ben Wagner145dbcd2016-11-03 14:40:50 -0400294 const SkImageInfo info = standardize_info(partialCodec.get());
scroggo8e6c7ad2016-09-16 08:20:38 -0700295 SkBitmap incremental;
296 incremental.allocPixels(info);
297
298 const SkCodec::Result startResult = partialCodec->startIncrementalDecode(info,
299 incremental.getPixels(), incremental.rowBytes());
300 if (startResult != SkCodec::kSuccess) {
301 ERRORF(r, "Failed to start incremental decode\n");
302 return;
303 }
304
305 SkCodec::Result result = partialCodec->incrementalDecode();
306 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
307
308 SkBitmap full;
309 full.allocPixels(info);
310 result = partialCodec->getPixels(info, full.getPixels(), full.rowBytes());
311 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
312
313 // Now incremental decode will fail
314 result = partialCodec->incrementalDecode();
315 REPORTER_ASSERT(r, result == SkCodec::kInvalidParameters);
316}
317
318DEF_TEST(Codec_rewind, r) {
319 test_interleaved(r, "plane.png");
320 test_interleaved(r, "plane_interlaced.png");
scroggo19b91532016-10-24 09:03:26 -0700321 test_interleaved(r, "box.gif");
scroggo8e6c7ad2016-09-16 08:20:38 -0700322}
Leon Scroggins III3fc97d72016-12-09 16:39:33 -0500323
324// Modified version of the giflib logo, from
325// http://giflib.sourceforge.net/whatsinagif/bits_and_bytes.html
326// The global color map has been replaced with a local color map.
327static unsigned char gNoGlobalColorMap[] = {
328 // Header
329 0x47, 0x49, 0x46, 0x38, 0x39, 0x61,
330
331 // Logical screen descriptor
332 0x0A, 0x00, 0x0A, 0x00, 0x11, 0x00, 0x00,
333
334 // Image descriptor
335 0x2C, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x81,
336
337 // Local color table
338 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00,
339
340 // Image data
341 0x02, 0x16, 0x8C, 0x2D, 0x99, 0x87, 0x2A, 0x1C, 0xDC, 0x33, 0xA0, 0x02, 0x75,
342 0xEC, 0x95, 0xFA, 0xA8, 0xDE, 0x60, 0x8C, 0x04, 0x91, 0x4C, 0x01, 0x00,
343
344 // Trailer
345 0x3B,
346};
347
348// Test that a gif file truncated before its local color map behaves as expected.
349DEF_TEST(Codec_GifPreMap, r) {
350 sk_sp<SkData> data = SkData::MakeWithoutCopy(gNoGlobalColorMap, sizeof(gNoGlobalColorMap));
351 std::unique_ptr<SkCodec> codec(SkCodec::NewFromData(data));
352 if (!codec) {
353 ERRORF(r, "failed to create codec");
354 return;
355 }
356
357 SkBitmap truth;
358 auto info = standardize_info(codec.get());
359 truth.allocPixels(info);
360
361 auto result = codec->getPixels(info, truth.getPixels(), truth.rowBytes());
362 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
363
364 // Truncate to 23 bytes, just before the color map. This should fail to decode.
365 codec.reset(SkCodec::NewFromData(SkData::MakeWithoutCopy(gNoGlobalColorMap, 23)));
366 REPORTER_ASSERT(r, codec);
367 if (codec) {
368 SkBitmap bm;
369 bm.allocPixels(info);
370 result = codec->getPixels(info, bm.getPixels(), bm.rowBytes());
371 REPORTER_ASSERT(r, result == SkCodec::kInvalidInput);
372 }
373
374 // Again, truncate to 23 bytes, this time for an incremental decode. We
375 // cannot start an incremental decode until we have more data. If we did,
376 // we would be using the wrong color table.
377 HaltingStream* stream = new HaltingStream(data, 23);
378 codec.reset(SkCodec::NewFromStream(stream));
379 REPORTER_ASSERT(r, codec);
380 if (codec) {
381 SkBitmap bm;
382 bm.allocPixels(info);
383 result = codec->startIncrementalDecode(info, bm.getPixels(), bm.rowBytes());
384 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
385
386 stream->addNewData(data->size());
387 result = codec->startIncrementalDecode(info, bm.getPixels(), bm.rowBytes());
388 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
389
390 result = codec->incrementalDecode();
391 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
392 compare_bitmaps(r, truth, bm);
393 }
394}