• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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