1 /*
2 * Copyright 2013 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 "include/codec/SkAndroidCodec.h"
9 #include "include/core/SkBitmap.h"
10 #include "include/core/SkCanvas.h"
11 #include "include/core/SkData.h"
12 #include "include/core/SkImage.h"
13 #include "include/core/SkStream.h"
14 #include "include/core/SkTypes.h"
15 #include "tests/CodecPriv.h"
16 #include "tests/Test.h"
17 #include "tools/Resources.h"
18
19 static unsigned char gGIFData[] = {
20 0x47, 0x49, 0x46, 0x38, 0x37, 0x61, 0x03, 0x00, 0x03, 0x00, 0xe3, 0x08,
21 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0x00,
22 0xff, 0x80, 0x80, 0x80, 0x00, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
23 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
24 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
25 0xff, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x04,
26 0x07, 0x50, 0x1c, 0x43, 0x40, 0x41, 0x23, 0x44, 0x00, 0x3b
27 };
28
29 static unsigned char gGIFDataNoColormap[] = {
30 // Header
31 0x47, 0x49, 0x46, 0x38, 0x39, 0x61,
32 // Screen descriptor
33 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
34 // Graphics control extension
35 0x21, 0xf9, 0x04, 0x01, 0x0a, 0x00, 0x01, 0x00,
36 // Image descriptor
37 0x2c, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
38 // Image data
39 0x02, 0x02, 0x4c, 0x01, 0x00,
40 // Trailer
41 0x3b
42 };
43
44 static unsigned char gInterlacedGIF[] = {
45 0x47, 0x49, 0x46, 0x38, 0x37, 0x61, 0x09, 0x00, 0x09, 0x00, 0xe3, 0x08, 0x00,
46 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0x00, 0xff, 0x80,
47 0x80, 0x80, 0x00, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
48 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
49 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x2c, 0x00, 0x00, 0x00,
50 0x00, 0x09, 0x00, 0x09, 0x00, 0x40, 0x04, 0x1b, 0x50, 0x1c, 0x23, 0xe9, 0x44,
51 0x23, 0x60, 0x9d, 0x09, 0x28, 0x1e, 0xf8, 0x6d, 0x64, 0x56, 0x9d, 0x53, 0xa8,
52 0x7e, 0xa8, 0x65, 0x94, 0x5c, 0xb0, 0x8a, 0x45, 0x04, 0x00, 0x3b
53 };
54
test_gif_data_no_colormap(skiatest::Reporter * r,void * data,size_t size)55 static void test_gif_data_no_colormap(skiatest::Reporter* r,
56 void* data,
57 size_t size) {
58 SkBitmap bm;
59 bool imageDecodeSuccess = decode_memory(data, size, &bm);
60 REPORTER_ASSERT(r, imageDecodeSuccess);
61 REPORTER_ASSERT(r, bm.width() == 1);
62 REPORTER_ASSERT(r, bm.height() == 1);
63 REPORTER_ASSERT(r, !(bm.empty()));
64 if (!(bm.empty())) {
65 REPORTER_ASSERT(r, bm.getColor(0, 0) == 0x00000000);
66 }
67 }
test_gif_data(skiatest::Reporter * r,void * data,size_t size)68 static void test_gif_data(skiatest::Reporter* r, void* data, size_t size) {
69 SkBitmap bm;
70 bool imageDecodeSuccess = decode_memory(data, size, &bm);
71 REPORTER_ASSERT(r, imageDecodeSuccess);
72 REPORTER_ASSERT(r, bm.width() == 3);
73 REPORTER_ASSERT(r, bm.height() == 3);
74 REPORTER_ASSERT(r, !(bm.empty()));
75 if (!(bm.empty())) {
76 REPORTER_ASSERT(r, bm.getColor(0, 0) == 0xffff0000);
77 REPORTER_ASSERT(r, bm.getColor(1, 0) == 0xffffff00);
78 REPORTER_ASSERT(r, bm.getColor(2, 0) == 0xff00ffff);
79 REPORTER_ASSERT(r, bm.getColor(0, 1) == 0xff808080);
80 REPORTER_ASSERT(r, bm.getColor(1, 1) == 0xff000000);
81 REPORTER_ASSERT(r, bm.getColor(2, 1) == 0xff00ff00);
82 REPORTER_ASSERT(r, bm.getColor(0, 2) == 0xffffffff);
83 REPORTER_ASSERT(r, bm.getColor(1, 2) == 0xffff00ff);
84 REPORTER_ASSERT(r, bm.getColor(2, 2) == 0xff0000ff);
85 }
86 }
test_gif_data_dims(skiatest::Reporter * r,void * data,size_t size,int width,int height)87 static void test_gif_data_dims(skiatest::Reporter* r, void* data, size_t size, int width,
88 int height) {
89 SkBitmap bm;
90 bool imageDecodeSuccess = decode_memory(data, size, &bm);
91 REPORTER_ASSERT(r, imageDecodeSuccess);
92 REPORTER_ASSERT(r, bm.width() == width);
93 REPORTER_ASSERT(r, bm.height() == height);
94 REPORTER_ASSERT(r, !(bm.empty()));
95 }
test_interlaced_gif_data(skiatest::Reporter * r,void * data,size_t size)96 static void test_interlaced_gif_data(skiatest::Reporter* r,
97 void* data,
98 size_t size) {
99 SkBitmap bm;
100 bool imageDecodeSuccess = decode_memory(data, size, &bm);
101 REPORTER_ASSERT(r, imageDecodeSuccess);
102 REPORTER_ASSERT(r, bm.width() == 9);
103 REPORTER_ASSERT(r, bm.height() == 9);
104 REPORTER_ASSERT(r, !(bm.empty()));
105 if (!(bm.empty())) {
106 REPORTER_ASSERT(r, bm.getColor(0, 0) == 0xffff0000);
107 REPORTER_ASSERT(r, bm.getColor(1, 0) == 0xffffff00);
108 REPORTER_ASSERT(r, bm.getColor(2, 0) == 0xff00ffff);
109
110 REPORTER_ASSERT(r, bm.getColor(0, 2) == 0xffffffff);
111 REPORTER_ASSERT(r, bm.getColor(1, 2) == 0xffff00ff);
112 REPORTER_ASSERT(r, bm.getColor(2, 2) == 0xff0000ff);
113
114 REPORTER_ASSERT(r, bm.getColor(0, 4) == 0xff808080);
115 REPORTER_ASSERT(r, bm.getColor(1, 4) == 0xff000000);
116 REPORTER_ASSERT(r, bm.getColor(2, 4) == 0xff00ff00);
117
118 REPORTER_ASSERT(r, bm.getColor(0, 6) == 0xffff0000);
119 REPORTER_ASSERT(r, bm.getColor(1, 6) == 0xffffff00);
120 REPORTER_ASSERT(r, bm.getColor(2, 6) == 0xff00ffff);
121
122 REPORTER_ASSERT(r, bm.getColor(0, 8) == 0xffffffff);
123 REPORTER_ASSERT(r, bm.getColor(1, 8) == 0xffff00ff);
124 REPORTER_ASSERT(r, bm.getColor(2, 8) == 0xff0000ff);
125 }
126 }
127
test_gif_data_short(skiatest::Reporter * r,void * data,size_t size)128 static void test_gif_data_short(skiatest::Reporter* r,
129 void* data,
130 size_t size) {
131 SkBitmap bm;
132 bool imageDecodeSuccess = decode_memory(data, size, &bm);
133 REPORTER_ASSERT(r, imageDecodeSuccess);
134 REPORTER_ASSERT(r, bm.width() == 3);
135 REPORTER_ASSERT(r, bm.height() == 3);
136 REPORTER_ASSERT(r, !(bm.empty()));
137 if (!(bm.empty())) {
138 REPORTER_ASSERT(r, bm.getColor(0, 0) == 0xffff0000);
139 REPORTER_ASSERT(r, bm.getColor(1, 0) == 0xffffff00);
140 REPORTER_ASSERT(r, bm.getColor(2, 0) == 0xff00ffff);
141 REPORTER_ASSERT(r, bm.getColor(0, 1) == 0xff808080);
142 REPORTER_ASSERT(r, bm.getColor(1, 1) == 0xff000000);
143 REPORTER_ASSERT(r, bm.getColor(2, 1) == 0xff00ff00);
144 }
145 }
146
147 /**
148 This test will test the ability of the SkCodec to deal with
149 GIF files which have been mangled somehow. We want to display as
150 much of the GIF as possible.
151 */
DEF_TEST(Gif,reporter)152 DEF_TEST(Gif, reporter) {
153 // test perfectly good images.
154 test_gif_data(reporter, static_cast<void *>(gGIFData), sizeof(gGIFData));
155 test_interlaced_gif_data(reporter, static_cast<void *>(gInterlacedGIF),
156 sizeof(gInterlacedGIF));
157
158 unsigned char badData[sizeof(gGIFData)];
159
160 memcpy(badData, gGIFData, sizeof(gGIFData));
161 badData[6] = 0x01; // image too wide
162 test_gif_data(reporter, static_cast<void *>(badData), sizeof(gGIFData));
163 // "libgif warning [image too wide, expanding output to size]"
164
165 memcpy(badData, gGIFData, sizeof(gGIFData));
166 badData[8] = 0x01; // image too tall
167 test_gif_data(reporter, static_cast<void *>(badData), sizeof(gGIFData));
168 // "libgif warning [image too tall, expanding output to size]"
169
170 memcpy(badData, gGIFData, sizeof(gGIFData));
171 badData[62] = 0x01; // image shifted right
172 test_gif_data_dims(reporter, static_cast<void *>(badData), sizeof(gGIFData), 4, 3);
173
174 memcpy(badData, gGIFData, sizeof(gGIFData));
175 badData[64] = 0x01; // image shifted down
176 test_gif_data_dims(reporter, static_cast<void *>(badData), sizeof(gGIFData), 3, 4);
177
178 memcpy(badData, gGIFData, sizeof(gGIFData));
179 badData[62] = 0xff; // image shifted right
180 badData[63] = 0xff;
181 test_gif_data_dims(reporter, static_cast<void *>(badData), sizeof(gGIFData), 3 + 0xFFFF, 3);
182
183 memcpy(badData, gGIFData, sizeof(gGIFData));
184 badData[64] = 0xff; // image shifted down
185 badData[65] = 0xff;
186 test_gif_data_dims(reporter, static_cast<void *>(badData), sizeof(gGIFData), 3, 3 + 0xFFFF);
187
188 test_gif_data_no_colormap(reporter, static_cast<void *>(gGIFDataNoColormap),
189 sizeof(gGIFDataNoColormap));
190
191 #ifdef SK_HAS_WUFFS_LIBRARY
192 // We are transitioning from an old GIF implementation to a new (Wuffs) GIF
193 // implementation.
194 //
195 // This test (without SK_HAS_WUFFS_LIBRARY) is overly specific to the old
196 // implementation. It claims that, for invalid (truncated) input, we can
197 // still 'decode' all of the pixels because no matter what palette index
198 // each pixel is, they're all equivalently transparent. It's not obvious
199 // that this off-spec behavior is worth preserving. Are real world users
200 // decoding truncated all-transparent GIF images??
201 //
202 // Once the transition is complete, we can remove the #ifdef and delete the
203 // #else branch.
204 #else
205 // Since there is no color map, we do not even need to parse the image data
206 // to know that we should draw transparent. Truncate the file before the
207 // data. This should still succeed.
208 test_gif_data_no_colormap(reporter, static_cast<void *>(gGIFDataNoColormap), 31);
209
210 // Likewise, incremental decoding should succeed here.
211 {
212 sk_sp<SkData> data = SkData::MakeWithoutCopy(gGIFDataNoColormap, 31);
213 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(data));
214 REPORTER_ASSERT(reporter, codec);
215 if (codec) {
216 auto info = codec->getInfo().makeColorType(kN32_SkColorType);
217 SkBitmap bm;
218 bm.allocPixels(info);
219 REPORTER_ASSERT(reporter, SkCodec::kSuccess == codec->startIncrementalDecode(
220 info, bm.getPixels(), bm.rowBytes()));
221 REPORTER_ASSERT(reporter, SkCodec::kSuccess == codec->incrementalDecode());
222 REPORTER_ASSERT(reporter, bm.width() == 1);
223 REPORTER_ASSERT(reporter, bm.height() == 1);
224 REPORTER_ASSERT(reporter, !(bm.empty()));
225 if (!(bm.empty())) {
226 REPORTER_ASSERT(reporter, bm.getColor(0, 0) == 0x00000000);
227 }
228 }
229 }
230 #endif
231
232 // test short Gif. 80 is missing a few bytes.
233 test_gif_data_short(reporter, static_cast<void *>(gGIFData), 80);
234 // "libgif warning [DGifGetLine]"
235
236 test_interlaced_gif_data(reporter, static_cast<void *>(gInterlacedGIF),
237 100); // 100 is missing a few bytes
238 // "libgif warning [interlace DGifGetLine]"
239 }
240
DEF_TEST(Codec_GifInterlacedTruncated,r)241 DEF_TEST(Codec_GifInterlacedTruncated, r) {
242 // Check that gInterlacedGIF is exactly 102 bytes long, and that the final
243 // 30 bytes, in the half-open range [72, 102), consists of 0x1b (indicating
244 // a block of 27 bytes), then those 27 bytes, then 0x00 (end of the blocks)
245 // then 0x3b (end of the GIF).
246 if ((sizeof(gInterlacedGIF) != 102) ||
247 (gInterlacedGIF[72] != 0x1b) ||
248 (gInterlacedGIF[100] != 0x00) ||
249 (gInterlacedGIF[101] != 0x3b)) {
250 ERRORF(r, "Invalid gInterlacedGIF data");
251 return;
252 }
253
254 // We want to test the GIF codec's output on some (but not all) of the
255 // LZW-compressed data. As is, there is only one block of LZW-compressed
256 // data, 27 bytes long. Wuffs can output partial results from a partial
257 // block, but some other GIF implementations output intermediate rows only
258 // on block boundaries, so truncating to a prefix of gInterlacedGIF isn't
259 // enough. We also have to modify the block size down from 0x1b so that the
260 // edited version still contains a complete block. In this case, it's a
261 // block of 10 bytes.
262 unsigned char data[83];
263 memcpy(data, gInterlacedGIF, sizeof(data));
264 data[72] = sizeof(data) - 73;
265
266 // Just like test_interlaced_gif_data, check that we get a 9x9 image.
267 SkBitmap bm;
268 bool imageDecodeSuccess = decode_memory(data, sizeof(data), &bm);
269 REPORTER_ASSERT(r, imageDecodeSuccess);
270 REPORTER_ASSERT(r, bm.width() == 9);
271 REPORTER_ASSERT(r, bm.height() == 9);
272
273 // For an interlaced, non-transparent image, we thicken or replicate the
274 // rows of earlier interlace passes so that, when e.g. decoding a GIF
275 // sourced from a slow network connection, we show a richer intermediate
276 // image while waiting for the complete image. This replication is
277 // sometimes described as a "Haeberli inspired technique".
278 //
279 // For a 9 pixel high image, interlacing shuffles the row order to be: 0,
280 // 8, 4, 2, 6, 1, 3, 5, 7. Even though truncating to 10 bytes of
281 // LZW-compressed data only explicitly contains completed rows 0 and 8, we
282 // still expect row 7 to be set, due to replication, and therefore not
283 // transparent black (zero).
284 REPORTER_ASSERT(r, bm.getColor(0, 7) != 0);
285 }
286
287 // Regression test for decoding a gif image with sampleSize of 4, which was
288 // previously crashing.
DEF_TEST(Gif_Sampled,r)289 DEF_TEST(Gif_Sampled, r) {
290 auto data = GetResourceAsData("images/test640x479.gif");
291 REPORTER_ASSERT(r, data);
292 if (!data) {
293 return;
294 }
295 std::unique_ptr<SkStreamAsset> stream(new SkMemoryStream(std::move(data)));
296 std::unique_ptr<SkAndroidCodec> codec(SkAndroidCodec::MakeFromStream(std::move(stream)));
297 REPORTER_ASSERT(r, codec);
298 if (!codec) {
299 return;
300 }
301
302 SkAndroidCodec::AndroidOptions options;
303 options.fSampleSize = 4;
304
305 SkBitmap bm;
306 bm.allocPixels(codec->getInfo());
307 const SkCodec::Result result = codec->getAndroidPixels(codec->getInfo(), bm.getPixels(),
308 bm.rowBytes(), &options);
309 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
310 }
311
312 // If a GIF file is truncated before the header for the first image is defined,
313 // we should not create an SkCodec.
DEF_TEST(Codec_GifTruncated,r)314 DEF_TEST(Codec_GifTruncated, r) {
315 sk_sp<SkData> data(GetResourceAsData("images/test640x479.gif"));
316 if (!data) {
317 return;
318 }
319
320 // This is right before the header for the first image.
321 data = SkData::MakeSubset(data.get(), 0, 446);
322 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(data));
323 REPORTER_ASSERT(r, !codec);
324 }
325
326 /*
327 For the Codec_GifTruncated2 test, immediately below,
328 resources/images/box.gif's first 23 bytes are:
329
330 00000000: 4749 4638 3961 c800 3700 203f 002c 0000 GIF89a..7. ?.,..
331 00000010: 0000 c800 3700 85 ....7..
332
333 The breakdown:
334
335 @000 6 bytes magic "GIF89a"
336 @006 7 bytes Logical Screen Descriptor: 0xC8 0x00 ... 0x00
337 - width = 200
338 - height = 55
339 - flags = 0x20
340 - background color index, pixel aspect ratio bytes ignored
341 @00D 10 bytes Image Descriptor header: 0x2C 0x00 ... 0x85
342 - origin_x = 0
343 - origin_y = 0
344 - width = 200
345 - height = 55
346 - flags = 0x85, local color table, 64 RGB entries
347
348 In particular, 23 bytes is after the header, but before the color table.
349 */
350
DEF_TEST(Codec_GifTruncated2,r)351 DEF_TEST(Codec_GifTruncated2, r) {
352 // Truncate box.gif at 21, 22 and 23 bytes.
353 //
354 // See also Codec_GifTruncated3 in this file, below.
355 //
356 // See also Codec_trunc in CodecAnimTest.cpp for this magic 23.
357 //
358 // See also Codec_GifPreMap in CodecPartialTest.cpp for this magic 23.
359 for (int i = 21; i < 24; i++) {
360 sk_sp<SkData> data(GetResourceAsData("images/box.gif"));
361 if (!data) {
362 return;
363 }
364
365 data = SkData::MakeSubset(data.get(), 0, i);
366 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(data));
367
368 if (i <= 21) {
369 if (codec) {
370 ERRORF(r, "Invalid data gave non-nullptr codec");
371 }
372 return;
373 }
374
375 if (!codec) {
376 ERRORF(r, "Failed to create codec with partial data (truncated at %d)", i);
377 return;
378 }
379
380 #ifdef SK_HAS_WUFFS_LIBRARY
381 // We are transitioning from an old GIF implementation to a new (Wuffs)
382 // GIF implementation.
383 //
384 // The input is truncated in the Image Descriptor, before the local
385 // color table, and before (21) or after (22, 23) the first frame's
386 // XYWH (left / top / width / height) can be decoded. A detailed
387 // breakdown of those 23 bytes is in a comment above this function.
388 //
389 // With the old implementation, this test claimed that "no frame is
390 // complete enough that it has its metadata". In terms of the
391 // underlying file format, this claim is true for truncating at 21
392 // bytes, but not true for 22 or 23.
393 //
394 // At 21 bytes, both the old and new implementation's MakeFromStream
395 // factory method returns a nullptr SkCodec*, because creating a
396 // SkCodec requires knowing the image width and height (as its
397 // constructor takes an SkEncodedInfo argument), and specifically for
398 // GIF, decoding the image width and height requires decoding the first
399 // frame's XYWH, as per
400 // https://raw.githubusercontent.com/google/wuffs/master/test/data/artificial/gif-frame-out-of-bounds.gif.make-artificial.txt
401 //
402 // At 22 or 23 bytes, the first frame is complete enough that we can
403 // fill in all of a SkCodec::FrameInfo's fields (other than
404 // fFullyReceived). Specifically, we can fill in fRequiredFrame and
405 // fAlphaType, even though we haven't yet decoded the frame's RGB
406 // palette entries, as we do know the frame rectangle and that every
407 // palette entry is fully opaque, due to the lack of a Graphic Control
408 // Extension before the Image Descriptor.
409 //
410 // The new implementation correctly reports that the first frame's
411 // metadata is complete enough. The old implementation does not.
412 //
413 // Once the transition is complete, we can remove the #ifdef and delete
414 // the #else code.
415 REPORTER_ASSERT(r, codec->getFrameCount() == 1);
416 #else
417 // The old implementation claimed:
418 //
419 // Although we correctly created a codec, no frame is
420 // complete enough that it has its metadata. Returning 0
421 // ensures that Chromium will not try to create a frame
422 // too early.
423 REPORTER_ASSERT(r, codec->getFrameCount() == 0);
424 #endif
425 }
426 }
427
428 #ifdef SK_HAS_WUFFS_LIBRARY
429 // This tests that, after truncating the input, the pixels are still
430 // zero-initialized. If you comment out the SkSampler::Fill call in
431 // SkWuffsCodec::onStartIncrementalDecode, the test could still pass (in a
432 // standard configuration) but should fail with the MSAN memory sanitizer.
DEF_TEST(Codec_GifTruncated3,r)433 DEF_TEST(Codec_GifTruncated3, r) {
434 sk_sp<SkData> data(GetResourceAsData("images/box.gif"));
435 if (!data) {
436 return;
437 }
438
439 data = SkData::MakeSubset(data.get(), 0, 23);
440 sk_sp<SkImage> image(SkImage::MakeFromEncoded(data));
441
442 if (!image) {
443 ERRORF(r, "Missing image");
444 return;
445 }
446
447 REPORTER_ASSERT(r, image->width() == 200);
448 REPORTER_ASSERT(r, image->height() == 55);
449
450 SkBitmap bm;
451 if (!bm.tryAllocPixels(SkImageInfo::MakeN32Premul(200, 55))) {
452 ERRORF(r, "Failed to allocate pixels");
453 return;
454 }
455
456 bm.eraseColor(SK_ColorTRANSPARENT);
457
458 SkCanvas canvas(bm);
459 canvas.drawImage(image, 0, 0);
460
461 for (int i = 0; i < image->width(); ++i)
462 for (int j = 0; j < image->height(); ++j) {
463 SkColor actual = SkUnPreMultiply::PMColorToColor(*bm.getAddr32(i, j));
464 if (actual != SK_ColorTRANSPARENT) {
465 ERRORF(r, "did not initialize pixels! %i, %i is %x", i, j, actual);
466 }
467 }
468 }
469 #endif
470
DEF_TEST(Codec_gif_out_of_palette,r)471 DEF_TEST(Codec_gif_out_of_palette, r) {
472 if (GetResourcePath().isEmpty()) {
473 return;
474 }
475
476 const char* path = "images/out-of-palette.gif";
477 auto data = GetResourceAsData(path);
478 if (!data) {
479 ERRORF(r, "failed to find %s", path);
480 return;
481 }
482
483 auto codec = SkCodec::MakeFromData(std::move(data));
484 if (!codec) {
485 ERRORF(r, "Could not create codec from %s", path);
486 return;
487 }
488
489 SkBitmap bm;
490 bm.allocPixels(codec->getInfo());
491 auto result = codec->getPixels(bm.pixmap());
492 REPORTER_ASSERT(r, result == SkCodec::kSuccess, "Failed to decode %s with error %s",
493 path, SkCodec::ResultToString(result));
494
495 struct {
496 int x;
497 int y;
498 SkColor expected;
499 } pixels[] = {
500 { 0, 0, SK_ColorBLACK },
501 { 1, 0, SK_ColorWHITE },
502 { 0, 1, SK_ColorTRANSPARENT },
503 { 1, 1, SK_ColorTRANSPARENT },
504 };
505 for (auto& pixel : pixels) {
506 auto actual = bm.getColor(pixel.x, pixel.y);
507 REPORTER_ASSERT(r, actual == pixel.expected,
508 "pixel (%i,%i) mismatch! expected: %x actual: %x",
509 pixel.x, pixel.y, pixel.expected, actual);
510 }
511 }
512
513 // This tests decoding the GIF image created by this script:
514 // https://raw.githubusercontent.com/google/wuffs/6c2fb9a2fd9e3334ee7dabc1ad60bfc89158084f/test/data/artificial/gif-transparent-index.gif.make-artificial.txt
515 //
516 // It is a 4x2 animated image with 2 frames. The first frame is full of various
517 // red pixels. The second frame overlays a 3x1 rectangle at (1, 1): light blue,
518 // transparent, dark blue.
DEF_TEST(Codec_AnimatedTransparentGif,r)519 DEF_TEST(Codec_AnimatedTransparentGif, r) {
520 const char* path = "images/gif-transparent-index.gif";
521 auto data = GetResourceAsData(path);
522 if (!data) {
523 ERRORF(r, "failed to find %s", path);
524 return;
525 }
526
527 auto codec = SkCodec::MakeFromData(std::move(data));
528 if (!codec) {
529 ERRORF(r, "Could not create codec from %s", path);
530 return;
531 }
532
533 SkImageInfo info = codec->getInfo();
534 if ((info.width() != 4) || (info.height() != 2) || (codec->getFrameInfo().size() != 2)) {
535 ERRORF(r, "Unexpected image info");
536 return;
537 }
538
539 for (bool use565 : { false, true }) {
540 SkBitmap bm;
541 bm.allocPixels(use565 ? info.makeColorType(kRGB_565_SkColorType) : info);
542
543 for (int i = 0; i < 2; i++) {
544 SkCodec::Options options;
545 options.fFrameIndex = i;
546 options.fPriorFrame = (i > 0) ? (i - 1) : SkCodec::kNoFrame;
547 auto result = codec->getPixels(bm.pixmap(), &options);
548 #ifdef SK_HAS_WUFFS_LIBRARY
549 // No-op. Wuffs' GIF decoder supports animated 565.
550 #else
551 if (use565 && i > 0) {
552 // Unsupported. Quoting libgifcodec/SkLibGifCodec.cpp:
553 //
554 // In theory, we might be able to support this, but it's not
555 // clear that it is necessary (Chromium does not decode to 565,
556 // and Android does not decode frames beyond the first).
557 REPORTER_ASSERT(r, result != SkCodec::kSuccess,
558 "Unexpected success to decode frame %i", i);
559 continue;
560 }
561 #endif
562 REPORTER_ASSERT(r, result == SkCodec::kSuccess, "Failed to decode frame %i", i);
563
564 // Per above: the first frame is full of various red pixels.
565 SkColor expectedPixels[2][4] = {
566 { 0xFF800000, 0xFF900000, 0xFFA00000, 0xFFB00000 },
567 { 0xFFC00000, 0xFFD00000, 0xFFE00000, 0xFFF00000 },
568 };
569 if (use565) {
570 // For kRGB_565_SkColorType, copy the red channel's high 3 bits
571 // to its low 3 bits.
572 expectedPixels[0][0] = 0xFF840000;
573 expectedPixels[0][1] = 0xFF940000;
574 expectedPixels[0][2] = 0xFFA50000;
575 expectedPixels[0][3] = 0xFFB50000;
576 expectedPixels[1][0] = 0xFFC60000;
577 expectedPixels[1][1] = 0xFFD60000;
578 expectedPixels[1][2] = 0xFFE70000;
579 expectedPixels[1][3] = 0xFFF70000;
580 }
581 if (i > 0) {
582 // Per above: the second frame overlays a 3x1 rectangle at (1,
583 // 1): light blue, transparent, dark blue.
584 //
585 // Again, for kRGB_565_SkColorType, copy the blue channel's
586 // high 3 bits to its low 3 bits.
587 expectedPixels[1][1] = use565 ? 0xFF0000FF : 0xFF0000FF;
588 expectedPixels[1][3] = use565 ? 0xFF000052 : 0xFF000055;
589 }
590
591 for (int y = 0; y < 2; y++) {
592 for (int x = 0; x < 4; x++) {
593 auto expected = expectedPixels[y][x];
594 auto actual = bm.getColor(x, y);
595 REPORTER_ASSERT(r, actual == expected,
596 "use565 %i, frame %i, pixel (%i,%i) "
597 "mismatch! expected: %x actual: %x",
598 (int)use565, i, x, y, expected, actual);
599 }
600 }
601 }
602 }
603 }
604
605 // This test verifies that a GIF frame outside the image dimensions is handled
606 // as desired:
607 // - The image reports a size of 0 x 0, but the first frame is 100 x 90. The
608 // image (or "canvas") is expanded to fit the first frame. The first frame is red.
609 // - The second frame is a green 75 x 75 rectangle, reporting its x-offset and
610 // y-offset to be 105, placing it off screen. The decoder interprets this as no
611 // change from the first frame.
DEF_TEST(Codec_xOffsetTooBig,r)612 DEF_TEST(Codec_xOffsetTooBig, r) {
613 const char* path = "images/xOffsetTooBig.gif";
614 auto data = GetResourceAsData(path);
615 if (!data) {
616 ERRORF(r, "failed to find %s", path);
617 return;
618 }
619
620 auto codec = SkCodec::MakeFromData(std::move(data));
621 if (!codec) {
622 ERRORF(r, "Could not create codec from %s", path);
623 return;
624 }
625
626 REPORTER_ASSERT(r, codec->getFrameCount() == 2);
627
628 auto info = codec->getInfo();
629 REPORTER_ASSERT(r, info.width() == 100 && info.height() == 90);
630
631 SkBitmap bm;
632 bm.allocPixels(info);
633 for (int i = 0; i < 2; i++) {
634 SkCodec::FrameInfo frameInfo;
635 REPORTER_ASSERT(r, codec->getFrameInfo(i, &frameInfo));
636
637 SkIRect expectedRect = i == 0 ? SkIRect{0, 0, 100, 90} : SkIRect{100, 90, 100, 90};
638 REPORTER_ASSERT(r, expectedRect == frameInfo.fFrameRect);
639
640 SkCodec::Options options;
641 options.fFrameIndex = i;
642 REPORTER_ASSERT(r, SkCodec::kSuccess == codec->getPixels(bm.pixmap(), &options));
643
644 REPORTER_ASSERT(r, bm.getColor(0, 0) == SK_ColorRED);
645 }
646 }
647