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 "SkMakeUnique.h"
13 #include "SkRWBuffer.h"
14 #include "SkString.h"
15
16 #include "FakeStreams.h"
17 #include "Resources.h"
18 #include "Test.h"
19
standardize_info(SkCodec * codec)20 static SkImageInfo standardize_info(SkCodec* codec) {
21 SkImageInfo defaultInfo = codec->getInfo();
22 // Note: This drops the SkColorSpace, allowing the equality check between two
23 // different codecs created from the same file to have the same SkImageInfo.
24 return SkImageInfo::MakeN32Premul(defaultInfo.width(), defaultInfo.height());
25 }
26
create_truth(sk_sp<SkData> data,SkBitmap * dst)27 static bool create_truth(sk_sp<SkData> data, SkBitmap* dst) {
28 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(std::move(data)));
29 if (!codec) {
30 return false;
31 }
32
33 const SkImageInfo info = standardize_info(codec.get());
34 dst->allocPixels(info);
35 return SkCodec::kSuccess == codec->getPixels(info, dst->getPixels(), dst->rowBytes());
36 }
37
compare_bitmaps(skiatest::Reporter * r,const SkBitmap & bm1,const SkBitmap & bm2)38 static void compare_bitmaps(skiatest::Reporter* r, const SkBitmap& bm1, const SkBitmap& bm2) {
39 const SkImageInfo& info = bm1.info();
40 if (info != bm2.info()) {
41 ERRORF(r, "Bitmaps have different image infos!");
42 return;
43 }
44 const size_t rowBytes = info.minRowBytes();
45 for (int i = 0; i < info.height(); i++) {
46 if (memcmp(bm1.getAddr(0, i), bm2.getAddr(0, i), rowBytes)) {
47 ERRORF(r, "Bitmaps have different pixels, starting on line %i!", i);
48 return;
49 }
50 }
51 }
52
test_partial(skiatest::Reporter * r,const char * name,size_t minBytes=0)53 static void test_partial(skiatest::Reporter* r, const char* name, size_t minBytes = 0) {
54 sk_sp<SkData> file = GetResourceAsData(name);
55 if (!file) {
56 SkDebugf("missing resource %s\n", name);
57 return;
58 }
59
60 SkBitmap truth;
61 if (!create_truth(file, &truth)) {
62 ERRORF(r, "Failed to decode %s\n", name);
63 return;
64 }
65
66 // Now decode part of the file
67 HaltingStream* stream = new HaltingStream(file, SkTMax(file->size() / 2, minBytes));
68
69 // Note that we cheat and hold on to a pointer to stream, though it is owned by
70 // partialCodec.
71 std::unique_ptr<SkCodec> partialCodec(SkCodec::MakeFromStream(std::unique_ptr<SkStream>(stream)));
72 if (!partialCodec) {
73 // Technically, this could be a small file where half the file is not
74 // enough.
75 ERRORF(r, "Failed to create codec for %s", name);
76 return;
77 }
78
79 const SkImageInfo info = standardize_info(partialCodec.get());
80 SkASSERT(info == truth.info());
81 SkBitmap incremental;
82 incremental.allocPixels(info);
83
84 while (true) {
85 const SkCodec::Result startResult = partialCodec->startIncrementalDecode(info,
86 incremental.getPixels(), incremental.rowBytes());
87 if (startResult == SkCodec::kSuccess) {
88 break;
89 }
90
91 if (stream->isAllDataReceived()) {
92 ERRORF(r, "Failed to start incremental decode\n");
93 return;
94 }
95
96 // Append some data. The size is arbitrary, but deliberately different from
97 // the buffer size used by SkPngCodec.
98 stream->addNewData(1000);
99 }
100
101 while (true) {
102 const SkCodec::Result result = partialCodec->incrementalDecode();
103
104 if (result == SkCodec::kSuccess) {
105 break;
106 }
107
108 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
109
110 if (stream->isAllDataReceived()) {
111 ERRORF(r, "Failed to completely decode %s", name);
112 return;
113 }
114
115 // Append some data. The size is arbitrary, but deliberately different from
116 // the buffer size used by SkPngCodec.
117 stream->addNewData(1000);
118 }
119
120 // compare to original
121 compare_bitmaps(r, truth, incremental);
122 }
123
DEF_TEST(Codec_partial,r)124 DEF_TEST(Codec_partial, r) {
125 #if 0
126 // FIXME (scroggo): SkPngCodec needs to use SkStreamBuffer in order to
127 // support incremental decoding.
128 test_partial(r, "images/plane.png");
129 test_partial(r, "images/plane_interlaced.png");
130 test_partial(r, "images/yellow_rose.png");
131 test_partial(r, "images/index8.png");
132 test_partial(r, "images/color_wheel.png");
133 test_partial(r, "images/mandrill_256.png");
134 test_partial(r, "images/mandrill_32.png");
135 test_partial(r, "images/arrow.png");
136 test_partial(r, "images/randPixels.png");
137 test_partial(r, "images/baby_tux.png");
138 #endif
139 test_partial(r, "images/box.gif");
140 test_partial(r, "images/randPixels.gif", 215);
141 test_partial(r, "images/color_wheel.gif");
142 }
143
144 // Verify that when decoding an animated gif byte by byte we report the correct
145 // fRequiredFrame as soon as getFrameInfo reports the frame.
DEF_TEST(Codec_requiredFrame,r)146 DEF_TEST(Codec_requiredFrame, r) {
147 auto path = "images/colorTables.gif";
148 sk_sp<SkData> file = GetResourceAsData(path);
149 if (!file) {
150 return;
151 }
152
153 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(file));
154 if (!codec) {
155 ERRORF(r, "Failed to create codec from %s", path);
156 return;
157 }
158
159 auto frameInfo = codec->getFrameInfo();
160 if (frameInfo.size() <= 1) {
161 ERRORF(r, "Test is uninteresting with 0 or 1 frames");
162 return;
163 }
164
165 HaltingStream* stream(nullptr);
166 std::unique_ptr<SkCodec> partialCodec(nullptr);
167 for (size_t i = 0; !partialCodec; i++) {
168 if (file->size() == i) {
169 ERRORF(r, "Should have created a partial codec for %s", path);
170 return;
171 }
172 stream = new HaltingStream(file, i);
173 partialCodec = SkCodec::MakeFromStream(std::unique_ptr<SkStream>(stream));
174 }
175
176 std::vector<SkCodec::FrameInfo> partialInfo;
177 size_t frameToCompare = 0;
178 for (; stream->getLength() <= file->size(); stream->addNewData(1)) {
179 partialInfo = partialCodec->getFrameInfo();
180 for (; frameToCompare < partialInfo.size(); frameToCompare++) {
181 REPORTER_ASSERT(r, partialInfo[frameToCompare].fRequiredFrame
182 == frameInfo[frameToCompare].fRequiredFrame);
183 }
184
185 if (frameToCompare == frameInfo.size()) {
186 break;
187 }
188 }
189 }
190
DEF_TEST(Codec_partialAnim,r)191 DEF_TEST(Codec_partialAnim, r) {
192 auto path = "images/test640x479.gif";
193 sk_sp<SkData> file = GetResourceAsData(path);
194 if (!file) {
195 return;
196 }
197
198 // This stream will be owned by fullCodec, but we hang on to the pointer
199 // to determine frame offsets.
200 std::unique_ptr<SkCodec> fullCodec(SkCodec::MakeFromStream(skstd::make_unique<SkMemoryStream>(file)));
201 const auto info = standardize_info(fullCodec.get());
202
203 // frameByteCounts stores the number of bytes to decode a particular frame.
204 // - [0] is the number of bytes for the header
205 // - frames[i] requires frameByteCounts[i+1] bytes to decode
206 const std::vector<size_t> frameByteCounts = { 455, 69350, 1344, 1346, 1327 };
207 std::vector<SkBitmap> frames;
208 for (size_t i = 0; true; i++) {
209 SkBitmap frame;
210 frame.allocPixels(info);
211
212 SkCodec::Options opts;
213 opts.fFrameIndex = i;
214 const SkCodec::Result result = fullCodec->getPixels(info, frame.getPixels(),
215 frame.rowBytes(), &opts);
216
217 if (result == SkCodec::kIncompleteInput || result == SkCodec::kInvalidInput) {
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::MakeFromStream(
240 std::unique_ptr<SkStream>(haltingStream)));
241 if (!partialCodec) {
242 ERRORF(r, "Failed to create a partial codec from %s with %i bytes out of %i",
243 path, frameByteCounts[0], file->size());
244 return;
245 }
246
247 SkASSERT(frameByteCounts.size() > frames.size());
248 for (size_t i = 0; i < frames.size(); i++) {
249 const size_t fullFrameBytes = frameByteCounts[i + 1];
250 const size_t firstHalf = fullFrameBytes / 2;
251 const size_t secondHalf = fullFrameBytes - firstHalf;
252
253 haltingStream->addNewData(firstHalf);
254 auto frameInfo = partialCodec->getFrameInfo();
255 REPORTER_ASSERT(r, frameInfo.size() == i + 1);
256 REPORTER_ASSERT(r, !frameInfo[i].fFullyReceived);
257
258 SkBitmap frame;
259 frame.allocPixels(info);
260
261 SkCodec::Options opts;
262 opts.fFrameIndex = i;
263 SkCodec::Result result = partialCodec->startIncrementalDecode(info,
264 frame.getPixels(), frame.rowBytes(), &opts);
265 if (result != SkCodec::kSuccess) {
266 ERRORF(r, "Failed to start incremental decode for %s on frame %i",
267 path, i);
268 return;
269 }
270
271 result = partialCodec->incrementalDecode();
272 REPORTER_ASSERT(r, SkCodec::kIncompleteInput == result);
273
274 haltingStream->addNewData(secondHalf);
275 result = partialCodec->incrementalDecode();
276 REPORTER_ASSERT(r, SkCodec::kSuccess == result);
277
278 frameInfo = partialCodec->getFrameInfo();
279 REPORTER_ASSERT(r, frameInfo.size() == i + 1);
280 REPORTER_ASSERT(r, frameInfo[i].fFullyReceived);
281 compare_bitmaps(r, frames[i], frame);
282 }
283 }
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.
test_interleaved(skiatest::Reporter * r,const char * name)288 static void test_interleaved(skiatest::Reporter* r, const char* name) {
289 sk_sp<SkData> file = GetResourceAsData(name);
290 if (!file) {
291 return;
292 }
293 const size_t halfSize = file->size() / 2;
294 std::unique_ptr<SkCodec> partialCodec(SkCodec::MakeFromStream(
295 skstd::make_unique<HaltingStream>(std::move(file), halfSize)));
296 if (!partialCodec) {
297 ERRORF(r, "Failed to create codec for %s", name);
298 return;
299 }
300
301 const SkImageInfo info = standardize_info(partialCodec.get());
302 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
DEF_TEST(Codec_rewind,r)325 DEF_TEST(Codec_rewind, r) {
326 test_interleaved(r, "images/plane.png");
327 test_interleaved(r, "images/plane_interlaced.png");
328 test_interleaved(r, "images/box.gif");
329 }
330
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.
334 static 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.
DEF_TEST(Codec_GifPreMap,r)356 DEF_TEST(Codec_GifPreMap, r) {
357 sk_sp<SkData> data = SkData::MakeWithoutCopy(gNoGlobalColorMap, sizeof(gNoGlobalColorMap));
358 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(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 = SkCodec::MakeFromData(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 = SkCodec::MakeFromStream(std::unique_ptr<SkStream>(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 }
402
DEF_TEST(Codec_emptyIDAT,r)403 DEF_TEST(Codec_emptyIDAT, r) {
404 const char* name = "images/baby_tux.png";
405 sk_sp<SkData> file = GetResourceAsData(name);
406 if (!file) {
407 return;
408 }
409
410 // Truncate to the beginning of the IDAT, immediately after the IDAT tag.
411 file = SkData::MakeSubset(file.get(), 0, 80);
412
413 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(std::move(file)));
414 if (!codec) {
415 ERRORF(r, "Failed to create a codec for %s", name);
416 return;
417 }
418
419 SkBitmap bm;
420 const auto info = standardize_info(codec.get());
421 bm.allocPixels(info);
422
423 const auto result = codec->getPixels(info, bm.getPixels(), bm.rowBytes());
424 REPORTER_ASSERT(r, SkCodec::kIncompleteInput == result);
425 }
426
DEF_TEST(Codec_incomplete,r)427 DEF_TEST(Codec_incomplete, r) {
428 for (const char* name : { "images/baby_tux.png",
429 "images/baby_tux.webp",
430 "images/CMYK.jpg",
431 "images/color_wheel.gif",
432 "images/google_chrome.ico",
433 "images/rle.bmp",
434 "images/mandrill.wbmp",
435 }) {
436 sk_sp<SkData> file = GetResourceAsData(name);
437 if (!name) {
438 continue;
439 }
440
441 for (size_t len = 14; len <= file->size(); len += 5) {
442 SkCodec::Result result;
443 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromStream(
444 skstd::make_unique<SkMemoryStream>(file->data(), len), &result));
445 if (codec) {
446 if (result != SkCodec::kSuccess) {
447 ERRORF(r, "Created an SkCodec for %s with %lu bytes, but "
448 "reported an error %i", name, len, result);
449 }
450 break;
451 }
452
453 if (SkCodec::kIncompleteInput != result) {
454 ERRORF(r, "Reported error %i for %s with %lu bytes",
455 result, name, len);
456 break;
457 }
458 }
459 }
460 }
461