xref: /third_party/skia/tests/GifTest.cpp (revision cb93a386)
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
19static 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
29static 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
44static 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
55static 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}
68static 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}
87static 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}
96static 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
128static 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*/
152DEF_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
241DEF_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.
289DEF_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.
314DEF_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/*
327For the Codec_GifTruncated2 test, immediately below,
328resources/images/box.gif's first 23 bytes are:
329
33000000000: 4749 4638 3961 c800 3700 203f 002c 0000  GIF89a..7. ?.,..
33100000010: 0000 c800 3700 85                        ....7..
332
333The 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
348In particular, 23 bytes is after the header, but before the color table.
349*/
350
351DEF_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.
433DEF_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
471DEF_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.
519DEF_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.
612DEF_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