1 /*
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 "FakeStreams.h"
16 #include "Resources.h"
17 #include "Test.h"
18
standardize_info(SkCodec * codec)19 static 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
create_truth(sk_sp<SkData> data,SkBitmap * dst)26 static bool create_truth(sk_sp<SkData> data, SkBitmap* dst) {
27 std::unique_ptr<SkCodec> codec(SkCodec::NewFromData(std::move(data)));
28 if (!codec) {
29 return false;
30 }
31
32 const SkImageInfo info = standardize_info(codec.get());
33 dst->allocPixels(info);
34 return SkCodec::kSuccess == codec->getPixels(info, dst->getPixels(), dst->rowBytes());
35 }
36
compare_bitmaps(skiatest::Reporter * r,const SkBitmap & bm1,const SkBitmap & bm2)37 static 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
test_partial(skiatest::Reporter * r,const char * name,size_t minBytes=0)49 static void test_partial(skiatest::Reporter* r, const char* name, size_t minBytes = 0) {
50 sk_sp<SkData> file = GetResourceAsData(name);
51 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
62 // Now decode part of the file
63 HaltingStream* stream = new HaltingStream(file, SkTMax(file->size() / 2, minBytes));
64
65 // Note that we cheat and hold on to a pointer to stream, though it is owned by
66 // partialCodec.
67 std::unique_ptr<SkCodec> partialCodec(SkCodec::NewFromStream(stream));
68 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
75 const SkImageInfo info = standardize_info(partialCodec.get());
76 SkASSERT(info == truth.info());
77 SkBitmap incremental;
78 incremental.allocPixels(info);
79
80 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);
95 }
96
97 while (true) {
98 const SkCodec::Result result = partialCodec->incrementalDecode();
99
100 if (result == SkCodec::kSuccess) {
101 break;
102 }
103
104 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
105
106 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);
114 }
115
116 // compare to original
117 compare_bitmaps(r, truth, incremental);
118 }
119
DEF_TEST(Codec_partial,r)120 DEF_TEST(Codec_partial, r) {
121 #if 0
122 // FIXME (scroggo): SkPngCodec needs to use SkStreamBuffer in order to
123 // support incremental decoding.
124 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");
134 #endif
135 test_partial(r, "box.gif");
136 test_partial(r, "randPixels.gif", 215);
137 test_partial(r, "color_wheel.gif");
138 }
139
140 // Verify that when decoding an animated gif byte by byte we report the correct
141 // fRequiredFrame as soon as getFrameInfo reports the frame.
DEF_TEST(Codec_requiredFrame,r)142 DEF_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
DEF_TEST(Codec_partialAnim,r)187 DEF_TEST(Codec_partialAnim, r) {
188 auto path = "test640x479.gif";
189 sk_sp<SkData> file = GetResourceAsData(path);
190 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
218 if (result == SkCodec::kIncompleteInput || result == SkCodec::kInvalidInput) {
219 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);
256 auto frameInfo = partialCodec->getFrameInfo();
257 REPORTER_ASSERT(r, frameInfo.size() == i + 1);
258 REPORTER_ASSERT(r, !frameInfo[i].fFullyReceived);
259
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
280 frameInfo = partialCodec->getFrameInfo();
281 REPORTER_ASSERT(r, frameInfo.size() == i + 1);
282 REPORTER_ASSERT(r, frameInfo[i].fFullyReceived);
283
284 // allocPixels locked the pixels for frame, but frames[i] was copied
285 // from another bitmap, and did not retain the locked status.
286 SkAutoLockPixels alp(frames[i]);
287 compare_bitmaps(r, frames[i], frame);
288 }
289 }
290
291 // Test that calling getPixels when an incremental decode has been
292 // started (but not finished) makes the next call to incrementalDecode
293 // require a call to startIncrementalDecode.
test_interleaved(skiatest::Reporter * r,const char * name)294 static void test_interleaved(skiatest::Reporter* r, const char* name) {
295 sk_sp<SkData> file = GetResourceAsData(name);
296 if (!file) {
297 return;
298 }
299 const size_t halfSize = file->size() / 2;
300 std::unique_ptr<SkCodec> partialCodec(SkCodec::NewFromStream(
301 new HaltingStream(std::move(file), halfSize)));
302 if (!partialCodec) {
303 ERRORF(r, "Failed to create codec for %s", name);
304 return;
305 }
306
307 const SkImageInfo info = standardize_info(partialCodec.get());
308 SkBitmap incremental;
309 incremental.allocPixels(info);
310
311 const SkCodec::Result startResult = partialCodec->startIncrementalDecode(info,
312 incremental.getPixels(), incremental.rowBytes());
313 if (startResult != SkCodec::kSuccess) {
314 ERRORF(r, "Failed to start incremental decode\n");
315 return;
316 }
317
318 SkCodec::Result result = partialCodec->incrementalDecode();
319 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
320
321 SkBitmap full;
322 full.allocPixels(info);
323 result = partialCodec->getPixels(info, full.getPixels(), full.rowBytes());
324 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
325
326 // Now incremental decode will fail
327 result = partialCodec->incrementalDecode();
328 REPORTER_ASSERT(r, result == SkCodec::kInvalidParameters);
329 }
330
DEF_TEST(Codec_rewind,r)331 DEF_TEST(Codec_rewind, r) {
332 test_interleaved(r, "plane.png");
333 test_interleaved(r, "plane_interlaced.png");
334 test_interleaved(r, "box.gif");
335 }
336
337 // Modified version of the giflib logo, from
338 // http://giflib.sourceforge.net/whatsinagif/bits_and_bytes.html
339 // The global color map has been replaced with a local color map.
340 static unsigned char gNoGlobalColorMap[] = {
341 // Header
342 0x47, 0x49, 0x46, 0x38, 0x39, 0x61,
343
344 // Logical screen descriptor
345 0x0A, 0x00, 0x0A, 0x00, 0x11, 0x00, 0x00,
346
347 // Image descriptor
348 0x2C, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x81,
349
350 // Local color table
351 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00,
352
353 // Image data
354 0x02, 0x16, 0x8C, 0x2D, 0x99, 0x87, 0x2A, 0x1C, 0xDC, 0x33, 0xA0, 0x02, 0x75,
355 0xEC, 0x95, 0xFA, 0xA8, 0xDE, 0x60, 0x8C, 0x04, 0x91, 0x4C, 0x01, 0x00,
356
357 // Trailer
358 0x3B,
359 };
360
361 // Test that a gif file truncated before its local color map behaves as expected.
DEF_TEST(Codec_GifPreMap,r)362 DEF_TEST(Codec_GifPreMap, r) {
363 sk_sp<SkData> data = SkData::MakeWithoutCopy(gNoGlobalColorMap, sizeof(gNoGlobalColorMap));
364 std::unique_ptr<SkCodec> codec(SkCodec::NewFromData(data));
365 if (!codec) {
366 ERRORF(r, "failed to create codec");
367 return;
368 }
369
370 SkBitmap truth;
371 auto info = standardize_info(codec.get());
372 truth.allocPixels(info);
373
374 auto result = codec->getPixels(info, truth.getPixels(), truth.rowBytes());
375 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
376
377 // Truncate to 23 bytes, just before the color map. This should fail to decode.
378 codec.reset(SkCodec::NewFromData(SkData::MakeWithoutCopy(gNoGlobalColorMap, 23)));
379 REPORTER_ASSERT(r, codec);
380 if (codec) {
381 SkBitmap bm;
382 bm.allocPixels(info);
383 result = codec->getPixels(info, bm.getPixels(), bm.rowBytes());
384 REPORTER_ASSERT(r, result == SkCodec::kInvalidInput);
385 }
386
387 // Again, truncate to 23 bytes, this time for an incremental decode. We
388 // cannot start an incremental decode until we have more data. If we did,
389 // we would be using the wrong color table.
390 HaltingStream* stream = new HaltingStream(data, 23);
391 codec.reset(SkCodec::NewFromStream(stream));
392 REPORTER_ASSERT(r, codec);
393 if (codec) {
394 SkBitmap bm;
395 bm.allocPixels(info);
396 result = codec->startIncrementalDecode(info, bm.getPixels(), bm.rowBytes());
397 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
398
399 stream->addNewData(data->size());
400 result = codec->startIncrementalDecode(info, bm.getPixels(), bm.rowBytes());
401 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
402
403 result = codec->incrementalDecode();
404 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
405 compare_bitmaps(r, truth, bm);
406 }
407 }
408
DEF_TEST(Codec_emptyIDAT,r)409 DEF_TEST(Codec_emptyIDAT, r) {
410 const char* name = "baby_tux.png";
411 sk_sp<SkData> file = GetResourceAsData(name);
412 if (!file) {
413 SkDebugf("REMOVE\n");
414 return;
415 }
416
417 // Truncate to the beginning of the IDAT, immediately after the IDAT tag.
418 file = SkData::MakeSubset(file.get(), 0, 80);
419
420 std::unique_ptr<SkCodec> codec(SkCodec::NewFromData(std::move(file)));
421 if (!codec) {
422 ERRORF(r, "Failed to create a codec for %s", name);
423 return;
424 }
425
426 SkBitmap bm;
427 const auto info = standardize_info(codec.get());
428 bm.allocPixels(info);
429
430 const auto result = codec->getPixels(info, bm.getPixels(), bm.rowBytes());
431 REPORTER_ASSERT(r, SkCodec::kIncompleteInput == result);
432 }
433