• 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  
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