• 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 "CodecPriv.h"
9 #include "Resources.h"
10 #include "SkAndroidCodec.h"
11 #include "SkBitmap.h"
12 #include "SkCanvas.h"
13 #include "SkData.h"
14 #include "SkImage.h"
15 #include "SkStream.h"
16 #include "SkTypes.h"
17 #include "Test.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 
241 // Regression test for decoding a gif image with sampleSize of 4, which was
242 // previously crashing.
DEF_TEST(Gif_Sampled,r)243 DEF_TEST(Gif_Sampled, r) {
244     auto data = GetResourceAsData("images/test640x479.gif");
245     REPORTER_ASSERT(r, data);
246     if (!data) {
247         return;
248     }
249     std::unique_ptr<SkStreamAsset> stream(new SkMemoryStream(std::move(data)));
250     std::unique_ptr<SkAndroidCodec> codec(SkAndroidCodec::MakeFromStream(std::move(stream)));
251     REPORTER_ASSERT(r, codec);
252     if (!codec) {
253         return;
254     }
255 
256     SkAndroidCodec::AndroidOptions options;
257     options.fSampleSize = 4;
258 
259     SkBitmap bm;
260     bm.allocPixels(codec->getInfo());
261     const SkCodec::Result result = codec->getAndroidPixels(codec->getInfo(), bm.getPixels(),
262             bm.rowBytes(), &options);
263     REPORTER_ASSERT(r, result == SkCodec::kSuccess);
264 }
265 
266 // If a GIF file is truncated before the header for the first image is defined,
267 // we should not create an SkCodec.
DEF_TEST(Codec_GifTruncated,r)268 DEF_TEST(Codec_GifTruncated, r) {
269     sk_sp<SkData> data(GetResourceAsData("images/test640x479.gif"));
270     if (!data) {
271         return;
272     }
273 
274     // This is right before the header for the first image.
275     data = SkData::MakeSubset(data.get(), 0, 446);
276     std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(data));
277     REPORTER_ASSERT(r, !codec);
278 }
279 
280 /*
281 For the Codec_GifTruncated2 test, immediately below,
282 resources/images/box.gif's first 23 bytes are:
283 
284 00000000: 4749 4638 3961 c800 3700 203f 002c 0000  GIF89a..7. ?.,..
285 00000010: 0000 c800 3700 85                        ....7..
286 
287 The breakdown:
288 
289 @000  6 bytes magic "GIF89a"
290 @006  7 bytes Logical Screen Descriptor: 0xC8 0x00 ... 0x00
291    - width     =   200
292    - height    =    55
293    - flags     =  0x20
294    - background color index, pixel aspect ratio bytes ignored
295 @00D 10 bytes Image Descriptor header: 0x2C 0x00 ... 0x85
296    - origin_x  =     0
297    - origin_y  =     0
298    - width     =   200
299    - height    =    55
300    - flags     =  0x85, local color table, 64 RGB entries
301 
302 In particular, 23 bytes is after the header, but before the color table.
303 */
304 
DEF_TEST(Codec_GifTruncated2,r)305 DEF_TEST(Codec_GifTruncated2, r) {
306     // Truncate box.gif at 21, 22 and 23 bytes.
307     //
308     // See also Codec_GifTruncated3 in this file, below.
309     //
310     // See also Codec_trunc in CodecAnimTest.cpp for this magic 23.
311     //
312     // See also Codec_GifPreMap in CodecPartialTest.cpp for this magic 23.
313     for (int i = 21; i < 24; i++) {
314         sk_sp<SkData> data(GetResourceAsData("images/box.gif"));
315         if (!data) {
316             return;
317         }
318 
319         data = SkData::MakeSubset(data.get(), 0, i);
320         std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(data));
321 
322         if (i <= 21) {
323             if (codec) {
324                 ERRORF(r, "Invalid data gave non-nullptr codec");
325             }
326             return;
327         }
328 
329         if (!codec) {
330             ERRORF(r, "Failed to create codec with partial data (truncated at %d)", i);
331             return;
332         }
333 
334 #ifdef SK_HAS_WUFFS_LIBRARY
335         // We are transitioning from an old GIF implementation to a new (Wuffs)
336         // GIF implementation.
337         //
338         // The input is truncated in the Image Descriptor, before the local
339         // color table, and before (21) or after (22, 23) the first frame's
340         // XYWH (left / top / width / height) can be decoded. A detailed
341         // breakdown of those 23 bytes is in a comment above this function.
342         //
343         // With the old implementation, this test claimed that "no frame is
344         // complete enough that it has its metadata". In terms of the
345         // underlying file format, this claim is true for truncating at 21
346         // bytes, but not true for 22 or 23.
347         //
348         // At 21 bytes, both the old and new implementation's MakeFromStream
349         // factory method returns a nullptr SkCodec*, because creating a
350         // SkCodec requires knowing the image width and height (as its
351         // constructor takes an SkEncodedInfo argument), and specifically for
352         // GIF, decoding the image width and height requires decoding the first
353         // frame's XYWH, as per
354         // https://raw.githubusercontent.com/google/wuffs/master/test/data/artificial/gif-frame-out-of-bounds.gif.make-artificial.txt
355         //
356         // At 22 or 23 bytes, the first frame is complete enough that we can
357         // fill in all of a SkCodec::FrameInfo's fields (other than
358         // fFullyReceived). Specifically, we can fill in fRequiredFrame and
359         // fAlphaType, even though we haven't yet decoded the frame's RGB
360         // palette entries, as we do know the frame rectangle and that every
361         // palette entry is fully opaque, due to the lack of a Graphic Control
362         // Extension before the Image Descriptor.
363         //
364         // The new implementation correctly reports that the first frame's
365         // metadata is complete enough. The old implementation does not.
366         //
367         // Once the transition is complete, we can remove the #ifdef and delete
368         // the #else code.
369         REPORTER_ASSERT(r, codec->getFrameCount() == 1);
370 #else
371         // The old implementation claimed:
372         //
373         // Although we correctly created a codec, no frame is
374         // complete enough that it has its metadata. Returning 0
375         // ensures that Chromium will not try to create a frame
376         // too early.
377         REPORTER_ASSERT(r, codec->getFrameCount() == 0);
378 #endif
379     }
380 }
381 
382 #ifdef SK_HAS_WUFFS_LIBRARY
383 // This tests that, after truncating the input, the pixels are still
384 // zero-initialized. If you comment out the SkSampler::Fill call in
385 // SkWuffsCodec::onStartIncrementalDecode, the test could still pass (in a
386 // standard configuration) but should fail with the MSAN memory sanitizer.
DEF_TEST(Codec_GifTruncated3,r)387 DEF_TEST(Codec_GifTruncated3, r) {
388     sk_sp<SkData> data(GetResourceAsData("images/box.gif"));
389     if (!data) {
390         return;
391     }
392 
393     data = SkData::MakeSubset(data.get(), 0, 23);
394     sk_sp<SkImage> image(SkImage::MakeFromEncoded(data));
395 
396     if (!image) {
397         ERRORF(r, "Missing image");
398         return;
399     }
400 
401     REPORTER_ASSERT(r, image->width() == 200);
402     REPORTER_ASSERT(r, image->height() == 55);
403 
404     SkBitmap bm;
405     if (!bm.tryAllocPixels(SkImageInfo::MakeN32Premul(200, 55))) {
406         ERRORF(r, "Failed to allocate pixels");
407         return;
408     }
409 
410     bm.eraseColor(SK_ColorTRANSPARENT);
411 
412     SkCanvas canvas(bm);
413     canvas.drawImage(image, 0, 0, nullptr);
414 
415     for (int i = 0; i < image->width();  ++i)
416     for (int j = 0; j < image->height(); ++j) {
417         SkColor actual = SkUnPreMultiply::PMColorToColor(*bm.getAddr32(i, j));
418         if (actual != SK_ColorTRANSPARENT) {
419             ERRORF(r, "did not initialize pixels! %i, %i is %x", i, j, actual);
420         }
421     }
422 }
423 #endif
424 
DEF_TEST(Codec_gif_out_of_palette,r)425 DEF_TEST(Codec_gif_out_of_palette, r) {
426     if (GetResourcePath().isEmpty()) {
427         return;
428     }
429 
430     const char* path = "images/out-of-palette.gif";
431     auto data = GetResourceAsData(path);
432     if (!data) {
433         ERRORF(r, "failed to find %s", path);
434         return;
435     }
436 
437     auto codec = SkCodec::MakeFromData(std::move(data));
438     if (!codec) {
439         ERRORF(r, "Could not create codec from %s", path);
440         return;
441     }
442 
443     SkBitmap bm;
444     bm.allocPixels(codec->getInfo());
445     auto result = codec->getPixels(bm.pixmap());
446     REPORTER_ASSERT(r, result == SkCodec::kSuccess, "Failed to decode %s with error %s",
447                     path, SkCodec::ResultToString(result));
448 
449     struct {
450         int     x;
451         int     y;
452         SkColor expected;
453     } pixels[] = {
454         { 0, 0, SK_ColorBLACK },
455         { 1, 0, SK_ColorWHITE },
456         { 0, 1, SK_ColorTRANSPARENT },
457         { 1, 1, SK_ColorTRANSPARENT },
458     };
459     for (auto& pixel : pixels) {
460         auto actual = bm.getColor(pixel.x, pixel.y);
461         REPORTER_ASSERT(r, actual == pixel.expected,
462                         "pixel (%i,%i) mismatch! expected: %x actual: %x",
463                         pixel.x, pixel.y, pixel.expected, actual);
464     }
465 }
466