blob: 329b9df4c9ef03924b0d5a531e05ec376751a125 [file] [log] [blame]
halcanary@google.com29d4e632013-10-11 18:21:56 +00001/*
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
Mike Kleinc0bd9f92019-04-23 12:05:21 -05008#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"
halcanary@google.com29d4e632013-10-11 18:21:56 +000018
tfarina@chromium.org58674812014-01-21 23:39:22 +000019static 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[] = {
Leon Scroggins III3fc97d72016-12-09 16:39:33 -050030 // 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
tfarina@chromium.org58674812014-01-21 23:39:22 +000042};
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};
halcanary@google.com29d4e632013-10-11 18:21:56 +000054
55static void test_gif_data_no_colormap(skiatest::Reporter* r,
tfarina@chromium.org58674812014-01-21 23:39:22 +000056 void* data,
57 size_t size) {
halcanary@google.com29d4e632013-10-11 18:21:56 +000058 SkBitmap bm;
msarett7f7ec202016-03-01 12:12:27 -080059 bool imageDecodeSuccess = decode_memory(data, size, &bm);
halcanary@google.com29d4e632013-10-11 18:21:56 +000060 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())) {
scroggo19b91532016-10-24 09:03:26 -070065 REPORTER_ASSERT(r, bm.getColor(0, 0) == 0x00000000);
halcanary@google.com29d4e632013-10-11 18:21:56 +000066 }
67}
68static void test_gif_data(skiatest::Reporter* r, void* data, size_t size) {
69 SkBitmap bm;
msarett7f7ec202016-03-01 12:12:27 -080070 bool imageDecodeSuccess = decode_memory(data, size, &bm);
halcanary@google.com29d4e632013-10-11 18:21:56 +000071 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}
msarett7f7ec202016-03-01 12:12:27 -080087static 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}
halcanary@google.com29d4e632013-10-11 18:21:56 +000096static void test_interlaced_gif_data(skiatest::Reporter* r,
97 void* data,
98 size_t size) {
99 SkBitmap bm;
msarett7f7ec202016-03-01 12:12:27 -0800100 bool imageDecodeSuccess = decode_memory(data, size, &bm);
halcanary@google.com29d4e632013-10-11 18:21:56 +0000101 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;
msarett7f7ec202016-03-01 12:12:27 -0800132 bool imageDecodeSuccess = decode_memory(data, size, &bm);
halcanary@google.com29d4e632013-10-11 18:21:56 +0000133 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/**
msarett7f7ec202016-03-01 12:12:27 -0800148 This test will test the ability of the SkCodec to deal with
halcanary@google.com29d4e632013-10-11 18:21:56 +0000149 GIF files which have been mangled somehow. We want to display as
150 much of the GIF as possible.
151*/
tfarina@chromium.orge4fafb12013-12-12 21:11:12 +0000152DEF_TEST(Gif, reporter) {
halcanary@google.com29d4e632013-10-11 18:21:56 +0000153 // test perfectly good images.
tfarina@chromium.org58674812014-01-21 23:39:22 +0000154 test_gif_data(reporter, static_cast<void *>(gGIFData), sizeof(gGIFData));
155 test_interlaced_gif_data(reporter, static_cast<void *>(gInterlacedGIF),
156 sizeof(gInterlacedGIF));
halcanary@google.com29d4e632013-10-11 18:21:56 +0000157
tfarina@chromium.org58674812014-01-21 23:39:22 +0000158 unsigned char badData[sizeof(gGIFData)];
halcanary@google.com29d4e632013-10-11 18:21:56 +0000159
tfarina@chromium.org58674812014-01-21 23:39:22 +0000160 memcpy(badData, gGIFData, sizeof(gGIFData));
halcanary@google.com29d4e632013-10-11 18:21:56 +0000161 badData[6] = 0x01; // image too wide
tfarina@chromium.org58674812014-01-21 23:39:22 +0000162 test_gif_data(reporter, static_cast<void *>(badData), sizeof(gGIFData));
halcanary@google.com29d4e632013-10-11 18:21:56 +0000163 // "libgif warning [image too wide, expanding output to size]"
164
tfarina@chromium.org58674812014-01-21 23:39:22 +0000165 memcpy(badData, gGIFData, sizeof(gGIFData));
halcanary@google.com29d4e632013-10-11 18:21:56 +0000166 badData[8] = 0x01; // image too tall
tfarina@chromium.org58674812014-01-21 23:39:22 +0000167 test_gif_data(reporter, static_cast<void *>(badData), sizeof(gGIFData));
halcanary@google.com29d4e632013-10-11 18:21:56 +0000168 // "libgif warning [image too tall, expanding output to size]"
169
tfarina@chromium.org58674812014-01-21 23:39:22 +0000170 memcpy(badData, gGIFData, sizeof(gGIFData));
halcanary@google.com29d4e632013-10-11 18:21:56 +0000171 badData[62] = 0x01; // image shifted right
msarett7f7ec202016-03-01 12:12:27 -0800172 test_gif_data_dims(reporter, static_cast<void *>(badData), sizeof(gGIFData), 4, 3);
halcanary@google.com29d4e632013-10-11 18:21:56 +0000173
tfarina@chromium.org58674812014-01-21 23:39:22 +0000174 memcpy(badData, gGIFData, sizeof(gGIFData));
halcanary@google.com29d4e632013-10-11 18:21:56 +0000175 badData[64] = 0x01; // image shifted down
msarett7f7ec202016-03-01 12:12:27 -0800176 test_gif_data_dims(reporter, static_cast<void *>(badData), sizeof(gGIFData), 3, 4);
halcanary@google.com29d4e632013-10-11 18:21:56 +0000177
tfarina@chromium.org58674812014-01-21 23:39:22 +0000178 memcpy(badData, gGIFData, sizeof(gGIFData));
msarett7f7ec202016-03-01 12:12:27 -0800179 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);
halcanary@google.com29d4e632013-10-11 18:21:56 +0000182
tfarina@chromium.org58674812014-01-21 23:39:22 +0000183 memcpy(badData, gGIFData, sizeof(gGIFData));
msarett7f7ec202016-03-01 12:12:27 -0800184 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);
halcanary@google.com29d4e632013-10-11 18:21:56 +0000187
tfarina@chromium.org58674812014-01-21 23:39:22 +0000188 test_gif_data_no_colormap(reporter, static_cast<void *>(gGIFDataNoColormap),
189 sizeof(gGIFDataNoColormap));
Leon Scroggins III3fc97d72016-12-09 16:39:33 -0500190
Leon Scroggins IIIe93ec682018-10-26 09:25:51 -0400191#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
Leon Scroggins III3fc97d72016-12-09 16:39:33 -0500205 // 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);
Mike Reedede7bac2017-07-23 15:30:02 -0400213 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(data));
Leon Scroggins III3fc97d72016-12-09 16:39:33 -0500214 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 }
Leon Scroggins IIIe93ec682018-10-26 09:25:51 -0400230#endif
halcanary@google.com29d4e632013-10-11 18:21:56 +0000231
232 // test short Gif. 80 is missing a few bytes.
tfarina@chromium.org58674812014-01-21 23:39:22 +0000233 test_gif_data_short(reporter, static_cast<void *>(gGIFData), 80);
halcanary@google.com29d4e632013-10-11 18:21:56 +0000234 // "libgif warning [DGifGetLine]"
235
tfarina@chromium.org58674812014-01-21 23:39:22 +0000236 test_interlaced_gif_data(reporter, static_cast<void *>(gInterlacedGIF),
halcanary@google.com29d4e632013-10-11 18:21:56 +0000237 100); // 100 is missing a few bytes
238 // "libgif warning [interlace DGifGetLine]"
239}
240
Nigel Tao3c70f9a2019-09-11 21:55:26 +1000241DEF_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
Nigel Taod48f13b2019-09-24 21:09:28 +1000256 // 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.
Nigel Tao3c70f9a2019-09-11 21:55:26 +1000262 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}
Nigel Tao3c70f9a2019-09-11 21:55:26 +1000286
scroggo9d214292015-05-14 14:44:13 -0700287// Regression test for decoding a gif image with sampleSize of 4, which was
288// previously crashing.
289DEF_TEST(Gif_Sampled, r) {
Mike Reed0933bc92017-12-09 01:27:41 +0000290 auto data = GetResourceAsData("images/test640x479.gif");
291 REPORTER_ASSERT(r, data);
292 if (!data) {
scroggo9d214292015-05-14 14:44:13 -0700293 return;
294 }
Mike Reed0933bc92017-12-09 01:27:41 +0000295 std::unique_ptr<SkStreamAsset> stream(new SkMemoryStream(std::move(data)));
Mike Reedede7bac2017-07-23 15:30:02 -0400296 std::unique_ptr<SkAndroidCodec> codec(SkAndroidCodec::MakeFromStream(std::move(stream)));
msarett7f7ec202016-03-01 12:12:27 -0800297 REPORTER_ASSERT(r, codec);
298 if (!codec) {
scroggo9d214292015-05-14 14:44:13 -0700299 return;
300 }
msarett7f7ec202016-03-01 12:12:27 -0800301
msarett7f7ec202016-03-01 12:12:27 -0800302 SkAndroidCodec::AndroidOptions options;
303 options.fSampleSize = 4;
msarett7f7ec202016-03-01 12:12:27 -0800304
scroggo9d214292015-05-14 14:44:13 -0700305 SkBitmap bm;
Leon Scroggins571b30f2017-07-11 17:35:31 +0000306 bm.allocPixels(codec->getInfo());
msarett7f7ec202016-03-01 12:12:27 -0800307 const SkCodec::Result result = codec->getAndroidPixels(codec->getInfo(), bm.getPixels(),
308 bm.rowBytes(), &options);
309 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
scroggo9d214292015-05-14 14:44:13 -0700310}
Leon Scroggins III4993b952016-12-08 11:54:04 -0500311
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) {
Hal Canaryc465d132017-12-08 10:21:31 -0500315 sk_sp<SkData> data(GetResourceAsData("images/test640x479.gif"));
Leon Scroggins IIIe726e7c2017-07-18 16:22:52 -0400316 if (!data) {
317 return;
318 }
Leon Scroggins III4993b952016-12-08 11:54:04 -0500319
320 // This is right before the header for the first image.
321 data = SkData::MakeSubset(data.get(), 0, 446);
Mike Reedede7bac2017-07-23 15:30:02 -0400322 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(data));
Leon Scroggins III4993b952016-12-08 11:54:04 -0500323 REPORTER_ASSERT(r, !codec);
324}
Leon Scroggins IIIe726e7c2017-07-18 16:22:52 -0400325
Leon Scroggins IIIe93ec682018-10-26 09:25:51 -0400326/*
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
Leon Scroggins IIIe726e7c2017-07-18 16:22:52 -0400351DEF_TEST(Codec_GifTruncated2, r) {
Leon Scroggins IIIe93ec682018-10-26 09:25:51 -0400352 // 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) {
Hal Canaryc465d132017-12-08 10:21:31 -0500434 sk_sp<SkData> data(GetResourceAsData("images/box.gif"));
Leon Scroggins IIIe726e7c2017-07-18 16:22:52 -0400435 if (!data) {
436 return;
437 }
438
Leon Scroggins IIIe726e7c2017-07-18 16:22:52 -0400439 data = SkData::MakeSubset(data.get(), 0, 23);
Leon Scroggins IIIe93ec682018-10-26 09:25:51 -0400440 sk_sp<SkImage> image(SkImage::MakeFromEncoded(data));
441
442 if (!image) {
443 ERRORF(r, "Missing image");
Leon Scroggins IIIe726e7c2017-07-18 16:22:52 -0400444 return;
445 }
446
Leon Scroggins IIIe93ec682018-10-26 09:25:51 -0400447 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);
Mike Reed99da77a2021-01-29 12:27:49 -0500459 canvas.drawImage(image, 0, 0);
Leon Scroggins IIIe93ec682018-10-26 09:25:51 -0400460
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 }
Leon Scroggins IIIe726e7c2017-07-18 16:22:52 -0400468}
Leon Scroggins IIIe93ec682018-10-26 09:25:51 -0400469#endif
Leon Scroggins IIIf05626b2018-10-11 13:50:28 -0400470
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}
Nigel Taobf4605f2020-09-28 21:53:07 +1000512
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 }
Nigel Taobf4605f2020-09-28 21:53:07 +1000538
Nigel Taof933e4f2020-10-29 09:42:11 +1100539 for (bool use565 : { false, true }) {
540 SkBitmap bm;
541 bm.allocPixels(use565 ? info.makeColorType(kRGB_565_SkColorType) : info);
Nigel Taobf4605f2020-09-28 21:53:07 +1000542
Nigel Taof933e4f2020-10-29 09:42:11 +1100543 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);
Nigel Taobf4605f2020-09-28 21:53:07 +1000563
Nigel Taof933e4f2020-10-29 09:42:11 +1100564 // 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 }
Nigel Taobf4605f2020-09-28 21:53:07 +1000600 }
601 }
602 }
603}
Leon Scroggins III469d67e2020-11-11 12:45:40 -0500604
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}