1 // Copyright 2019 The PDFium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "core/fxcodec/progressive_decoder.h"
6
7 #include <stddef.h>
8 #include <stdint.h>
9
10 #include <array>
11 #include <numeric>
12 #include <tuple>
13 #include <utility>
14
15 #include "core/fxcodec/fx_codec.h"
16 #include "core/fxcodec/fx_codec_def.h"
17 #include "core/fxcrt/cfx_read_only_span_stream.h"
18 #include "core/fxcrt/cfx_read_only_vector_stream.h"
19 #include "core/fxcrt/data_vector.h"
20 #include "core/fxcrt/retain_ptr.h"
21 #include "core/fxge/dib/cfx_dibitmap.h"
22 #include "core/fxge/dib/fx_dib.h"
23 #include "testing/gmock/include/gmock/gmock.h"
24 #include "testing/gtest/include/gtest/gtest.h"
25 #include "third_party/base/containers/span.h"
26
27 #ifdef PDF_ENABLE_XFA_BMP
28 #include "core/fxcodec/bmp/bmp_decoder.h"
29 #endif // PDF_ENABLE_XFA_BMP
30
31 #ifdef PDF_ENABLE_XFA_GIF
32 #include "core/fxcodec/gif/gif_decoder.h"
33 #endif // PDF_ENABLE_XFA_GIF
34
35 namespace fxcodec {
36
37 namespace {
38
39 using ::testing::ElementsAre;
40 using ::testing::ElementsAreArray;
41
42 template <size_t Size>
IotaArray(uint8_t start)43 constexpr std::array<uint8_t, Size> IotaArray(uint8_t start) {
44 std::array<uint8_t, Size> result;
45 std::iota(result.begin(), result.end(), start);
46 return result;
47 }
48
DecodeToBitmap(ProgressiveDecoder & decoder,const fxcrt::RetainPtr<CFX_DIBitmap> & bitmap)49 FXCODEC_STATUS DecodeToBitmap(ProgressiveDecoder& decoder,
50 const fxcrt::RetainPtr<CFX_DIBitmap>& bitmap) {
51 FXCODEC_STATUS status = decoder.StartDecode(bitmap, 0, 0, bitmap->GetWidth(),
52 bitmap->GetHeight());
53 while (status == FXCODEC_STATUS::kDecodeToBeContinued)
54 status = decoder.ContinueDecode();
55 return status;
56 }
57
58 } // namespace
59
60 #ifdef PDF_ENABLE_XFA_BMP
TEST(ProgressiveDecoder,Indexed8Bmp)61 TEST(ProgressiveDecoder, Indexed8Bmp) {
62 static constexpr uint8_t kInput[] = {
63 0x42, 0x4d, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a,
64 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
65 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00,
66 0x00, 0x04, 0x00, 0x00, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x13, 0x0b,
67 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xc0,
68 0x80, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00};
69
70 ProgressiveDecoder decoder;
71
72 auto source = pdfium::MakeRetain<CFX_ReadOnlySpanStream>(kInput);
73 CFX_DIBAttribute attr;
74 FXCODEC_STATUS status =
75 decoder.LoadImageInfo(std::move(source), FXCODEC_IMAGE_BMP, &attr, true);
76 ASSERT_EQ(FXCODEC_STATUS::kFrameReady, status);
77
78 ASSERT_EQ(1, decoder.GetWidth());
79 ASSERT_EQ(1, decoder.GetHeight());
80
81 auto bitmap = pdfium::MakeRetain<CFX_DIBitmap>();
82 bitmap->Create(decoder.GetWidth(), decoder.GetHeight(), FXDIB_Format::kRgb);
83
84 size_t frames;
85 std::tie(status, frames) = decoder.GetFrames();
86 ASSERT_EQ(FXCODEC_STATUS::kDecodeReady, status);
87 ASSERT_EQ(1u, frames);
88
89 status = DecodeToBitmap(decoder, bitmap);
90 EXPECT_EQ(FXCODEC_STATUS::kDecodeFinished, status);
91 EXPECT_THAT(bitmap->GetScanline(0), ElementsAre(0xc0, 0x80, 0x40, 0x00));
92 }
93
TEST(ProgressiveDecoder,Indexed8BmpWithInvalidIndex)94 TEST(ProgressiveDecoder, Indexed8BmpWithInvalidIndex) {
95 static constexpr uint8_t kInput[] = {
96 0x42, 0x4d, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a,
97 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
98 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00,
99 0x00, 0x04, 0x00, 0x00, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x13, 0x0b,
100 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xc0,
101 0x80, 0x40, 0x00, 0x01, 0x00, 0x00, 0x00};
102
103 ProgressiveDecoder decoder;
104
105 auto source = pdfium::MakeRetain<CFX_ReadOnlySpanStream>(kInput);
106 CFX_DIBAttribute attr;
107 FXCODEC_STATUS status =
108 decoder.LoadImageInfo(std::move(source), FXCODEC_IMAGE_BMP, &attr, true);
109 ASSERT_EQ(FXCODEC_STATUS::kFrameReady, status);
110
111 ASSERT_EQ(1, decoder.GetWidth());
112 ASSERT_EQ(1, decoder.GetHeight());
113
114 auto bitmap = pdfium::MakeRetain<CFX_DIBitmap>();
115 bitmap->Create(decoder.GetWidth(), decoder.GetHeight(), FXDIB_Format::kRgb);
116
117 size_t frames;
118 std::tie(status, frames) = decoder.GetFrames();
119 ASSERT_EQ(FXCODEC_STATUS::kDecodeReady, status);
120 ASSERT_EQ(1u, frames);
121
122 status = DecodeToBitmap(decoder, bitmap);
123 EXPECT_EQ(FXCODEC_STATUS::kError, status);
124 }
125
TEST(ProgressiveDecoder,Direct24Bmp)126 TEST(ProgressiveDecoder, Direct24Bmp) {
127 static constexpr uint8_t kInput[] = {
128 0x42, 0x4d, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00,
129 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00,
130 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00,
131 0x00, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x00, 0x00,
132 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x80, 0x40, 0x00};
133
134 ProgressiveDecoder decoder;
135
136 auto source = pdfium::MakeRetain<CFX_ReadOnlySpanStream>(kInput);
137 CFX_DIBAttribute attr;
138 FXCODEC_STATUS status =
139 decoder.LoadImageInfo(std::move(source), FXCODEC_IMAGE_BMP, &attr, true);
140 ASSERT_EQ(FXCODEC_STATUS::kFrameReady, status);
141
142 ASSERT_EQ(1, decoder.GetWidth());
143 ASSERT_EQ(1, decoder.GetHeight());
144
145 auto bitmap = pdfium::MakeRetain<CFX_DIBitmap>();
146 bitmap->Create(decoder.GetWidth(), decoder.GetHeight(), FXDIB_Format::kRgb);
147
148 size_t frames;
149 std::tie(status, frames) = decoder.GetFrames();
150 ASSERT_EQ(FXCODEC_STATUS::kDecodeReady, status);
151 ASSERT_EQ(1u, frames);
152
153 status = DecodeToBitmap(decoder, bitmap);
154 EXPECT_EQ(FXCODEC_STATUS::kDecodeFinished, status);
155 EXPECT_THAT(bitmap->GetScanline(0), ElementsAre(0xc0, 0x80, 0x40, 0x00));
156 }
157
TEST(ProgressiveDecoder,Direct32Bmp)158 TEST(ProgressiveDecoder, Direct32Bmp) {
159 static constexpr uint8_t kInput[] = {
160 0x42, 0x4d, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00,
161 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00,
162 0x00, 0x00, 0x01, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00,
163 0x00, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x00, 0x00,
164 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x80, 0x40, 0xff};
165
166 ProgressiveDecoder decoder;
167
168 auto source = pdfium::MakeRetain<CFX_ReadOnlySpanStream>(kInput);
169 CFX_DIBAttribute attr;
170 FXCODEC_STATUS status =
171 decoder.LoadImageInfo(std::move(source), FXCODEC_IMAGE_BMP, &attr, true);
172 ASSERT_EQ(FXCODEC_STATUS::kFrameReady, status);
173
174 ASSERT_EQ(1, decoder.GetWidth());
175 ASSERT_EQ(1, decoder.GetHeight());
176
177 auto bitmap = pdfium::MakeRetain<CFX_DIBitmap>();
178 bitmap->Create(decoder.GetWidth(), decoder.GetHeight(), FXDIB_Format::kRgb);
179
180 size_t frames;
181 std::tie(status, frames) = decoder.GetFrames();
182 ASSERT_EQ(FXCODEC_STATUS::kDecodeReady, status);
183 ASSERT_EQ(1u, frames);
184
185 status = DecodeToBitmap(decoder, bitmap);
186 EXPECT_EQ(FXCODEC_STATUS::kDecodeFinished, status);
187 EXPECT_THAT(bitmap->GetScanline(0), ElementsAre(0xc0, 0x80, 0x40, 0x00));
188 }
189
TEST(ProgressiveDecoder,BmpWithDataOffsetBeforeEndOfHeader)190 TEST(ProgressiveDecoder, BmpWithDataOffsetBeforeEndOfHeader) {
191 static constexpr uint8_t kInput[] = {
192 0x42, 0x4d, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00,
193 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00,
194 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00,
195 0x00, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x00, 0x00,
196 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x80, 0x40, 0x00};
197
198 ProgressiveDecoder decoder;
199
200 auto source = pdfium::MakeRetain<CFX_ReadOnlySpanStream>(kInput);
201 CFX_DIBAttribute attr;
202 FXCODEC_STATUS status =
203 decoder.LoadImageInfo(std::move(source), FXCODEC_IMAGE_BMP, &attr, true);
204 ASSERT_EQ(FXCODEC_STATUS::kFrameReady, status);
205
206 ASSERT_EQ(1, decoder.GetWidth());
207 ASSERT_EQ(1, decoder.GetHeight());
208
209 auto bitmap = pdfium::MakeRetain<CFX_DIBitmap>();
210 bitmap->Create(decoder.GetWidth(), decoder.GetHeight(), FXDIB_Format::kRgb);
211
212 size_t frames;
213 std::tie(status, frames) = decoder.GetFrames();
214 ASSERT_EQ(FXCODEC_STATUS::kDecodeReady, status);
215 ASSERT_EQ(1u, frames);
216
217 status = DecodeToBitmap(decoder, bitmap);
218 EXPECT_EQ(FXCODEC_STATUS::kDecodeFinished, status);
219 EXPECT_THAT(bitmap->GetScanline(0), ElementsAre(0xc0, 0x80, 0x40, 0x00));
220 }
221
TEST(ProgressiveDecoder,BmpWithDataOffsetAfterEndOfHeader)222 TEST(ProgressiveDecoder, BmpWithDataOffsetAfterEndOfHeader) {
223 static constexpr uint8_t kInput[] = {
224 0x42, 0x4d, 0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x00,
225 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00,
226 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00,
227 0x00, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x00, 0x00,
228 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x80, 0x40, 0x00};
229
230 ProgressiveDecoder decoder;
231
232 auto source = pdfium::MakeRetain<CFX_ReadOnlySpanStream>(kInput);
233 CFX_DIBAttribute attr;
234 FXCODEC_STATUS status =
235 decoder.LoadImageInfo(std::move(source), FXCODEC_IMAGE_BMP, &attr, true);
236 ASSERT_EQ(FXCODEC_STATUS::kFrameReady, status);
237
238 ASSERT_EQ(1, decoder.GetWidth());
239 ASSERT_EQ(1, decoder.GetHeight());
240
241 auto bitmap = pdfium::MakeRetain<CFX_DIBitmap>();
242 bitmap->Create(decoder.GetWidth(), decoder.GetHeight(), FXDIB_Format::kRgb);
243
244 size_t frames;
245 std::tie(status, frames) = decoder.GetFrames();
246 ASSERT_EQ(FXCODEC_STATUS::kDecodeReady, status);
247 ASSERT_EQ(1u, frames);
248
249 status = DecodeToBitmap(decoder, bitmap);
250 EXPECT_EQ(FXCODEC_STATUS::kDecodeFinished, status);
251 EXPECT_THAT(bitmap->GetScanline(0), ElementsAre(0xc0, 0x80, 0x40, 0x00));
252 }
253
TEST(ProgressiveDecoder,LargeBmp)254 TEST(ProgressiveDecoder, LargeBmp) {
255 // Construct a 24-bit BMP larger than `kBlockSize` (4096 bytes).
256 static constexpr uint8_t kWidth = 37;
257 static constexpr uint8_t kHeight = 38;
258 static constexpr size_t kScanlineSize = kWidth * 3 + 1;
259 DataVector<uint8_t> input = {
260 0x42, 0x4d, 0xd6, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36,
261 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, kWidth, 0x00, 0x00, 0x00,
262 kHeight, 0x00, 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00,
263 0x00, 0xa0, 0x10, 0x00, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x13, 0x0b,
264 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
265 input.resize(54 + kScanlineSize * kHeight);
266 std::iota(input.begin() + 54, input.end(), 0);
267 ASSERT_EQ(4310u, input.size());
268
269 ProgressiveDecoder decoder;
270
271 auto source = pdfium::MakeRetain<CFX_ReadOnlyVectorStream>(std::move(input));
272 CFX_DIBAttribute attr;
273 FXCODEC_STATUS status =
274 decoder.LoadImageInfo(std::move(source), FXCODEC_IMAGE_BMP, &attr, true);
275 ASSERT_EQ(FXCODEC_STATUS::kFrameReady, status);
276
277 ASSERT_EQ(kWidth, decoder.GetWidth());
278 ASSERT_EQ(kHeight, decoder.GetHeight());
279
280 auto bitmap = pdfium::MakeRetain<CFX_DIBitmap>();
281 bitmap->Create(decoder.GetWidth(), decoder.GetHeight(), FXDIB_Format::kRgb);
282
283 size_t frames;
284 std::tie(status, frames) = decoder.GetFrames();
285 ASSERT_EQ(FXCODEC_STATUS::kDecodeReady, status);
286 ASSERT_EQ(1u, frames);
287
288 status = DecodeToBitmap(decoder, bitmap);
289 EXPECT_EQ(FXCODEC_STATUS::kDecodeFinished, status);
290
291 for (size_t row = 0; row < kHeight; ++row) {
292 // BMP encodes rows from bottom to top by default.
293 pdfium::span<const uint8_t> scanline =
294 bitmap->GetScanline(kHeight - row - 1);
295
296 EXPECT_THAT(
297 scanline.subspan(0, kScanlineSize - 1),
298 ElementsAreArray(IotaArray<kScanlineSize - 1>(row * kScanlineSize)));
299
300 // Last byte is padding to a 32-bit boundary.
301 EXPECT_EQ(0, scanline[kScanlineSize - 1]);
302 }
303 }
304 #endif // PDF_ENABLE_XFA_BMP
305
306 #ifdef PDF_ENABLE_XFA_GIF
TEST(ProgressiveDecoder,Gif87a)307 TEST(ProgressiveDecoder, Gif87a) {
308 static constexpr uint8_t kInput[] = {
309 0x47, 0x49, 0x46, 0x38, 0x37, 0x61, 0x01, 0x00, 0x01, 0x00, 0x80, 0x01,
310 0x00, 0x40, 0x80, 0xc0, 0x80, 0x80, 0x80, 0x2c, 0x00, 0x00, 0x00, 0x00,
311 0x01, 0x00, 0x01, 0x00, 0x00, 0x02, 0x02, 0x44, 0x01, 0x00, 0x3b};
312
313 ProgressiveDecoder decoder;
314
315 auto source = pdfium::MakeRetain<CFX_ReadOnlySpanStream>(kInput);
316 CFX_DIBAttribute attr;
317 FXCODEC_STATUS status =
318 decoder.LoadImageInfo(std::move(source), FXCODEC_IMAGE_GIF, &attr, true);
319 ASSERT_EQ(FXCODEC_STATUS::kFrameReady, status);
320
321 ASSERT_EQ(1, decoder.GetWidth());
322 ASSERT_EQ(1, decoder.GetHeight());
323
324 auto bitmap = pdfium::MakeRetain<CFX_DIBitmap>();
325 bitmap->Create(decoder.GetWidth(), decoder.GetHeight(), FXDIB_Format::kArgb);
326
327 size_t frames;
328 std::tie(status, frames) = decoder.GetFrames();
329 ASSERT_EQ(FXCODEC_STATUS::kDecodeReady, status);
330 ASSERT_EQ(1u, frames);
331
332 status = DecodeToBitmap(decoder, bitmap);
333 EXPECT_EQ(FXCODEC_STATUS::kDecodeFinished, status);
334 EXPECT_THAT(bitmap->GetScanline(0), ElementsAre(0xc0, 0x80, 0x40, 0xff));
335 }
336
TEST(ProgressiveDecoder,Gif89a)337 TEST(ProgressiveDecoder, Gif89a) {
338 static constexpr uint8_t kInput[] = {
339 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x01, 0x00, 0x01, 0x00, 0x80,
340 0x01, 0x00, 0x40, 0x80, 0xc0, 0x80, 0x80, 0x80, 0x21, 0xf9, 0x04,
341 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x01,
342 0x00, 0x01, 0x00, 0x00, 0x02, 0x02, 0x44, 0x01, 0x00, 0x3b};
343
344 ProgressiveDecoder decoder;
345
346 auto source = pdfium::MakeRetain<CFX_ReadOnlySpanStream>(kInput);
347 CFX_DIBAttribute attr;
348 FXCODEC_STATUS status =
349 decoder.LoadImageInfo(std::move(source), FXCODEC_IMAGE_GIF, &attr, true);
350 ASSERT_EQ(FXCODEC_STATUS::kFrameReady, status);
351
352 ASSERT_EQ(1, decoder.GetWidth());
353 ASSERT_EQ(1, decoder.GetHeight());
354
355 auto bitmap = pdfium::MakeRetain<CFX_DIBitmap>();
356 bitmap->Create(decoder.GetWidth(), decoder.GetHeight(), FXDIB_Format::kArgb);
357
358 size_t frames;
359 std::tie(status, frames) = decoder.GetFrames();
360 ASSERT_EQ(FXCODEC_STATUS::kDecodeReady, status);
361 ASSERT_EQ(1u, frames);
362
363 status = DecodeToBitmap(decoder, bitmap);
364 EXPECT_EQ(FXCODEC_STATUS::kDecodeFinished, status);
365 EXPECT_THAT(bitmap->GetScanline(0), ElementsAre(0xc0, 0x80, 0x40, 0xff));
366 }
367
TEST(ProgressiveDecoder,GifInsufficientCodeSize)368 TEST(ProgressiveDecoder, GifInsufficientCodeSize) {
369 // This GIF causes `LZWDecompressor::Create()` to fail because the minimum
370 // code size is too small for the palette.
371 static constexpr uint8_t kInput[] = {
372 0x47, 0x49, 0x46, 0x38, 0x37, 0x61, 0x01, 0x00, 0x01, 0x00, 0x82,
373 0x01, 0x00, 0x40, 0x80, 0xc0, 0x80, 0x80, 0x80, 0x81, 0x81, 0x81,
374 0x82, 0x82, 0x82, 0x83, 0x83, 0x83, 0x84, 0x84, 0x84, 0x85, 0x85,
375 0x85, 0x86, 0x86, 0x86, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
376 0x01, 0x00, 0x00, 0x02, 0x2, 0x44, 0x01, 0x00, 0x3b};
377
378 ProgressiveDecoder decoder;
379
380 auto source = pdfium::MakeRetain<CFX_ReadOnlySpanStream>(kInput);
381 CFX_DIBAttribute attr;
382 FXCODEC_STATUS status =
383 decoder.LoadImageInfo(std::move(source), FXCODEC_IMAGE_GIF, &attr, true);
384 ASSERT_EQ(FXCODEC_STATUS::kFrameReady, status);
385
386 ASSERT_EQ(1, decoder.GetWidth());
387 ASSERT_EQ(1, decoder.GetHeight());
388
389 auto bitmap = pdfium::MakeRetain<CFX_DIBitmap>();
390 bitmap->Create(decoder.GetWidth(), decoder.GetHeight(), FXDIB_Format::kArgb);
391
392 size_t frames;
393 std::tie(status, frames) = decoder.GetFrames();
394 ASSERT_EQ(FXCODEC_STATUS::kDecodeReady, status);
395 ASSERT_EQ(1u, frames);
396
397 status = DecodeToBitmap(decoder, bitmap);
398 EXPECT_EQ(FXCODEC_STATUS::kError, status);
399 }
400
TEST(ProgressiveDecoder,GifDecodeAcrossScanlines)401 TEST(ProgressiveDecoder, GifDecodeAcrossScanlines) {
402 // This GIF contains an LZW code unit split across 2 scanlines. The decoder
403 // must continue decoding the second scanline using the residual data.
404 static constexpr uint8_t kInput[] = {
405 0x47, 0x49, 0x46, 0x38, 0x37, 0x61, 0x04, 0x00, 0x02, 0x00, 0x80, 0x01,
406 0x00, 0x40, 0x80, 0xc0, 0x80, 0x80, 0x80, 0x2c, 0x00, 0x00, 0x00, 0x00,
407 0x04, 0x00, 0x02, 0x00, 0x00, 0x02, 0x03, 0x84, 0x6f, 0x05, 0x00, 0x3b};
408
409 ProgressiveDecoder decoder;
410
411 auto source = pdfium::MakeRetain<CFX_ReadOnlySpanStream>(kInput);
412 CFX_DIBAttribute attr;
413 FXCODEC_STATUS status =
414 decoder.LoadImageInfo(std::move(source), FXCODEC_IMAGE_GIF, &attr, true);
415 ASSERT_EQ(FXCODEC_STATUS::kFrameReady, status);
416
417 ASSERT_EQ(4, decoder.GetWidth());
418 ASSERT_EQ(2, decoder.GetHeight());
419
420 auto bitmap = pdfium::MakeRetain<CFX_DIBitmap>();
421 bitmap->Create(decoder.GetWidth(), decoder.GetHeight(), FXDIB_Format::kArgb);
422
423 size_t frames;
424 std::tie(status, frames) = decoder.GetFrames();
425 ASSERT_EQ(FXCODEC_STATUS::kDecodeReady, status);
426 ASSERT_EQ(1u, frames);
427
428 status = DecodeToBitmap(decoder, bitmap);
429 EXPECT_EQ(FXCODEC_STATUS::kDecodeFinished, status);
430 EXPECT_THAT(bitmap->GetScanline(0),
431 ElementsAre(0xc0, 0x80, 0x40, 0xff, 0xc0, 0x80, 0x40, 0xff, 0xc0,
432 0x80, 0x40, 0xff, 0xc0, 0x80, 0x40, 0xff));
433 EXPECT_THAT(bitmap->GetScanline(1),
434 ElementsAre(0xc0, 0x80, 0x40, 0xff, 0xc0, 0x80, 0x40, 0xff, 0xc0,
435 0x80, 0x40, 0xff, 0xc0, 0x80, 0x40, 0xff));
436 }
437
TEST(ProgressiveDecoder,GifDecodeAcrossSubblocks)438 TEST(ProgressiveDecoder, GifDecodeAcrossSubblocks) {
439 // This GIF contains a scanline split across 2 data sub-blocks. The decoder
440 // must continue decoding in the second sub-block.
441 static constexpr uint8_t kInput[] = {
442 0x47, 0x49, 0x46, 0x38, 0x37, 0x61, 0x04, 0x00, 0x02, 0x00,
443 0x80, 0x01, 0x00, 0x40, 0x80, 0xc0, 0x80, 0x80, 0x80, 0x2c,
444 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x02,
445 0x02, 0x84, 0x6f, 0x01, 0x05, 0x00, 0x3b};
446
447 ProgressiveDecoder decoder;
448
449 auto source = pdfium::MakeRetain<CFX_ReadOnlySpanStream>(kInput);
450 CFX_DIBAttribute attr;
451 FXCODEC_STATUS status =
452 decoder.LoadImageInfo(std::move(source), FXCODEC_IMAGE_GIF, &attr, true);
453 ASSERT_EQ(FXCODEC_STATUS::kFrameReady, status);
454
455 ASSERT_EQ(4, decoder.GetWidth());
456 ASSERT_EQ(2, decoder.GetHeight());
457
458 auto bitmap = pdfium::MakeRetain<CFX_DIBitmap>();
459 bitmap->Create(decoder.GetWidth(), decoder.GetHeight(), FXDIB_Format::kArgb);
460
461 size_t frames;
462 std::tie(status, frames) = decoder.GetFrames();
463 ASSERT_EQ(FXCODEC_STATUS::kDecodeReady, status);
464 ASSERT_EQ(1u, frames);
465
466 status = DecodeToBitmap(decoder, bitmap);
467 EXPECT_EQ(FXCODEC_STATUS::kDecodeFinished, status);
468 EXPECT_THAT(bitmap->GetScanline(0),
469 ElementsAre(0xc0, 0x80, 0x40, 0xff, 0xc0, 0x80, 0x40, 0xff, 0xc0,
470 0x80, 0x40, 0xff, 0xc0, 0x80, 0x40, 0xff));
471 EXPECT_THAT(bitmap->GetScanline(1),
472 ElementsAre(0xc0, 0x80, 0x40, 0xff, 0xc0, 0x80, 0x40, 0xff, 0xc0,
473 0x80, 0x40, 0xff, 0xc0, 0x80, 0x40, 0xff));
474 }
475 #endif // PDF_ENABLE_XFA_GIF
476
477 } // namespace fxcodec
478