xref: /aosp_15_r20/external/skia/tests/CodecPartialTest.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2016 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/SkCodec.h"
9 #include "include/core/SkBitmap.h"
10 #include "include/core/SkData.h"
11 #include "include/core/SkImageInfo.h"
12 #include "include/core/SkRefCnt.h"
13 #include "include/core/SkStream.h"
14 #include "include/core/SkString.h"
15 #include "include/core/SkTypes.h"
16 #include "include/private/base/SkDebug.h"
17 #include "tests/CodecPriv.h"
18 #include "tests/FakeStreams.h"
19 #include "tests/Test.h"
20 #include "tools/Resources.h"
21 
22 #include <algorithm>
23 #include <cstring>
24 #include <initializer_list>
25 #include <memory>
26 #include <utility>
27 #include <vector>
28 
standardize_info(SkCodec * codec)29 static SkImageInfo standardize_info(SkCodec* codec) {
30     SkImageInfo defaultInfo = codec->getInfo();
31     // Note: This drops the SkColorSpace, allowing the equality check between two
32     // different codecs created from the same file to have the same SkImageInfo.
33     return SkImageInfo::MakeN32Premul(defaultInfo.width(), defaultInfo.height());
34 }
35 
create_truth(sk_sp<SkData> data,SkBitmap * dst)36 static bool create_truth(sk_sp<SkData> data, SkBitmap* dst) {
37     std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(std::move(data)));
38     if (!codec) {
39         return false;
40     }
41 
42     const SkImageInfo info = standardize_info(codec.get());
43     dst->allocPixels(info);
44     return SkCodec::kSuccess == codec->getPixels(info, dst->getPixels(), dst->rowBytes());
45 }
46 
compare_bitmaps(skiatest::Reporter * r,const SkBitmap & bm1,const SkBitmap & bm2)47 static bool compare_bitmaps(skiatest::Reporter* r, const SkBitmap& bm1, const SkBitmap& bm2) {
48     const SkImageInfo& info = bm1.info();
49     if (info != bm2.info()) {
50         ERRORF(r, "Bitmaps have different image infos!");
51         return false;
52     }
53     const size_t rowBytes = info.minRowBytes();
54     for (int i = 0; i < info.height(); i++) {
55         if (0 != memcmp(bm1.getAddr(0, i), bm2.getAddr(0, i), rowBytes)) {
56             ERRORF(r, "Bitmaps have different pixels, starting on line %i!", i);
57             return false;
58         }
59     }
60 
61     return true;
62 }
63 
test_partial(skiatest::Reporter * r,const char * name,const sk_sp<SkData> & file,size_t minBytes,size_t increment)64 static void test_partial(skiatest::Reporter* r, const char* name, const sk_sp<SkData>& file,
65                          size_t minBytes, size_t increment) {
66     SkBitmap truth;
67     if (!create_truth(file, &truth)) {
68         ERRORF(r, "Failed to decode %s\n", name);
69         return;
70     }
71 
72     // Now decode part of the file
73     HaltingStream* stream = new HaltingStream(file, minBytes);
74 
75     // Note that we cheat and hold on to a pointer to stream, though it is owned by
76     // partialCodec.
77     auto partialCodec = SkCodec::MakeFromStream(std::unique_ptr<SkStream>(stream));
78     if (!partialCodec) {
79         ERRORF(r, "Failed to create codec for %s with %zu bytes", name, minBytes);
80         return;
81     }
82 
83     const SkImageInfo info = standardize_info(partialCodec.get());
84     SkASSERT(info == truth.info());
85     SkBitmap incremental;
86     incremental.allocPixels(info);
87 
88     while (true) {
89         const SkCodec::Result startResult = partialCodec->startIncrementalDecode(info,
90                 incremental.getPixels(), incremental.rowBytes());
91         if (startResult == SkCodec::kSuccess) {
92             break;
93         }
94 
95         if (stream->isAllDataReceived()) {
96             ERRORF(r, "Failed to start incremental decode\n");
97             return;
98         }
99 
100         stream->addNewData(increment);
101     }
102 
103     while (true) {
104         // This imitates how Chromium calls getFrameCount before resuming a decode.
105         partialCodec->getFrameCount();
106 
107         const SkCodec::Result result = partialCodec->incrementalDecode();
108 
109         if (result == SkCodec::kSuccess) {
110             break;
111         }
112 
113         REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
114 
115         if (stream->isAllDataReceived()) {
116             ERRORF(r, "Failed to completely decode %s", name);
117             return;
118         }
119 
120         stream->addNewData(increment);
121     }
122 
123     // compare to original
124     compare_bitmaps(r, truth, incremental);
125 }
126 
test_partial(skiatest::Reporter * r,const char * name,size_t minBytes=0)127 static void test_partial(skiatest::Reporter* r, const char* name, size_t minBytes = 0) {
128     sk_sp<SkData> file = GetResourceAsData(name);
129     if (!file) {
130         SkDebugf("missing resource %s\n", name);
131         return;
132     }
133 
134     // This size is arbitrary, but deliberately different from the buffer size used by SkPngCodec.
135     constexpr size_t kIncrement = 1000;
136     test_partial(r, name, file, std::max(file->size() / 2, minBytes), kIncrement);
137 }
138 
DEF_TEST(Codec_partial,r)139 DEF_TEST(Codec_partial, r) {
140 #if 0
141     // FIXME (scroggo): SkPngCodec needs to use SkStreamBuffer in order to
142     // support incremental decoding.
143     test_partial(r, "images/plane.png");
144     test_partial(r, "images/plane_interlaced.png");
145     test_partial(r, "images/yellow_rose.png");
146     test_partial(r, "images/index8.png");
147     test_partial(r, "images/color_wheel.png");
148     test_partial(r, "images/mandrill_256.png");
149     test_partial(r, "images/mandrill_32.png");
150     test_partial(r, "images/arrow.png");
151     test_partial(r, "images/randPixels.png");
152     test_partial(r, "images/baby_tux.png");
153 #endif
154     test_partial(r, "images/box.gif");
155     test_partial(r, "images/randPixels.gif", 215);
156     test_partial(r, "images/color_wheel.gif");
157 }
158 
DEF_TEST(Codec_partialWuffs,r)159 DEF_TEST(Codec_partialWuffs, r) {
160     const char* path = "images/alphabetAnim.gif";
161     auto file = GetResourceAsData(path);
162     if (!file) {
163         ERRORF(r, "missing %s", path);
164     } else {
165         // This is the end of the first frame. SkCodec will treat this as a
166         // single frame gif.
167         file = SkData::MakeSubset(file.get(), 0, 153);
168         // Start with 100 to get a partial decode, then add the rest of the
169         // first frame to decode a full image.
170         test_partial(r, path, file, 100, 53);
171     }
172 }
173 
174 // Verify that when decoding an animated gif byte by byte we report the correct
175 // fRequiredFrame as soon as getFrameInfo reports the frame.
DEF_TEST(Codec_requiredFrame,r)176 DEF_TEST(Codec_requiredFrame, r) {
177     auto path = "images/colorTables.gif";
178     sk_sp<SkData> file = GetResourceAsData(path);
179     if (!file) {
180         return;
181     }
182 
183     std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(file));
184     if (!codec) {
185         ERRORF(r, "Failed to create codec from %s", path);
186         return;
187     }
188 
189     auto frameInfo = codec->getFrameInfo();
190     if (frameInfo.size() <= 1) {
191         ERRORF(r, "Test is uninteresting with 0 or 1 frames");
192         return;
193     }
194 
195     HaltingStream* stream(nullptr);
196     std::unique_ptr<SkCodec> partialCodec(nullptr);
197     for (size_t i = 0; !partialCodec; i++) {
198         if (file->size() == i) {
199             ERRORF(r, "Should have created a partial codec for %s", path);
200             return;
201         }
202         stream = new HaltingStream(file, i);
203         partialCodec = SkCodec::MakeFromStream(std::unique_ptr<SkStream>(stream));
204     }
205 
206     std::vector<SkCodec::FrameInfo> partialInfo;
207     size_t frameToCompare = 0;
208     while (true) {
209         partialInfo = partialCodec->getFrameInfo();
210         for (; frameToCompare < partialInfo.size(); frameToCompare++) {
211             REPORTER_ASSERT(r, partialInfo[frameToCompare].fRequiredFrame
212                                 == frameInfo[frameToCompare].fRequiredFrame);
213         }
214 
215         if (frameToCompare == frameInfo.size()) {
216             break;
217         }
218 
219         if (stream->getLength() == file->size()) {
220             ERRORF(r, "Should have found all frames for %s", path);
221             return;
222         }
223         stream->addNewData(1);
224     }
225 }
226 
DEF_TEST(Codec_partialAnim,r)227 DEF_TEST(Codec_partialAnim, r) {
228     auto path = "images/test640x479.gif";
229     sk_sp<SkData> file = GetResourceAsData(path);
230     if (!file) {
231         return;
232     }
233 
234     // This stream will be owned by fullCodec, but we hang on to the pointer
235     // to determine frame offsets.
236     std::unique_ptr<SkCodec> fullCodec(SkCodec::MakeFromStream(std::make_unique<SkMemoryStream>(file)));
237     const auto info = standardize_info(fullCodec.get());
238 
239     // frameByteCounts stores the number of bytes to decode a particular frame.
240     // - [0] is the number of bytes for the header
241     // - frames[i] requires frameByteCounts[i+1] bytes to decode
242     const std::vector<size_t> frameByteCounts = { 455, 69350, 1344, 1346, 1327 };
243     std::vector<SkBitmap> frames;
244     for (size_t i = 0; true; i++) {
245         SkBitmap frame;
246         frame.allocPixels(info);
247 
248         SkCodec::Options opts;
249         opts.fFrameIndex = i;
250         const SkCodec::Result result = fullCodec->getPixels(info, frame.getPixels(),
251                 frame.rowBytes(), &opts);
252 
253         if (result == SkCodec::kIncompleteInput || result == SkCodec::kInvalidInput) {
254             // We need to distinguish between a partial frame and no more frames.
255             // getFrameInfo lets us do this, since it tells the number of frames
256             // not considering whether they are complete.
257             // FIXME: Should we use a different Result?
258             if (fullCodec->getFrameInfo().size() > i) {
259                 // This is a partial frame.
260                 frames.push_back(frame);
261             }
262             break;
263         }
264 
265         if (result != SkCodec::kSuccess) {
266             ERRORF(r, "Failed to decode frame %zu from %s", i, path);
267             return;
268         }
269 
270         frames.push_back(frame);
271     }
272 
273     // Now decode frames partially, then completely, and compare to the original.
274     HaltingStream* haltingStream = new HaltingStream(file, frameByteCounts[0]);
275     std::unique_ptr<SkCodec> partialCodec(SkCodec::MakeFromStream(
276                                                       std::unique_ptr<SkStream>(haltingStream)));
277     if (!partialCodec) {
278         ERRORF(r, "Failed to create a partial codec from %s with %zu bytes out of %zu",
279                path, frameByteCounts[0], file->size());
280         return;
281     }
282 
283     SkASSERT(frameByteCounts.size() > frames.size());
284     for (size_t i = 0; i < frames.size(); i++) {
285         const size_t fullFrameBytes = frameByteCounts[i + 1];
286         const size_t firstHalf = fullFrameBytes / 2;
287         const size_t secondHalf = fullFrameBytes - firstHalf;
288 
289         haltingStream->addNewData(firstHalf);
290         auto frameInfo = partialCodec->getFrameInfo();
291         REPORTER_ASSERT(r, frameInfo.size() == i + 1);
292         REPORTER_ASSERT(r, !frameInfo[i].fFullyReceived);
293 
294         SkBitmap frame;
295         frame.allocPixels(info);
296 
297         SkCodec::Options opts;
298         opts.fFrameIndex = i;
299         SkCodec::Result result = partialCodec->startIncrementalDecode(info,
300                 frame.getPixels(), frame.rowBytes(), &opts);
301         if (result != SkCodec::kSuccess) {
302             ERRORF(r, "Failed to start incremental decode for %s on frame %zu",
303                    path, i);
304             return;
305         }
306 
307         result = partialCodec->incrementalDecode();
308         REPORTER_ASSERT(r, SkCodec::kIncompleteInput == result);
309 
310         haltingStream->addNewData(secondHalf);
311         result = partialCodec->incrementalDecode();
312         REPORTER_ASSERT(r, SkCodec::kSuccess == result);
313 
314         frameInfo = partialCodec->getFrameInfo();
315         REPORTER_ASSERT(r, frameInfo.size() == i + 1);
316         REPORTER_ASSERT(r, frameInfo[i].fFullyReceived);
317         if (!compare_bitmaps(r, frames[i], frame)) {
318             ERRORF(r, "\tfailure was on frame %zu", i);
319             SkString name = SkStringPrintf("expected_%zu", i);
320             write_bm(name.c_str(), frames[i]);
321 
322             name = SkStringPrintf("actual_%zu", i);
323             write_bm(name.c_str(), frame);
324         }
325     }
326 }
327 
328 // Test that calling getPixels when an incremental decode has been
329 // started (but not finished) makes the next call to incrementalDecode
330 // require a call to startIncrementalDecode.
test_interleaved(skiatest::Reporter * r,const char * name)331 static void test_interleaved(skiatest::Reporter* r, const char* name) {
332     sk_sp<SkData> file = GetResourceAsData(name);
333     if (!file) {
334         return;
335     }
336     const size_t halfSize = file->size() / 2;
337     std::unique_ptr<SkCodec> partialCodec(SkCodec::MakeFromStream(
338                                   std::make_unique<HaltingStream>(std::move(file), halfSize)));
339     if (!partialCodec) {
340         ERRORF(r, "Failed to create codec for %s", name);
341         return;
342     }
343 
344     const SkImageInfo info = standardize_info(partialCodec.get());
345     SkBitmap incremental;
346     incremental.allocPixels(info);
347 
348     const SkCodec::Result startResult = partialCodec->startIncrementalDecode(info,
349             incremental.getPixels(), incremental.rowBytes());
350     if (startResult != SkCodec::kSuccess) {
351         ERRORF(r, "Failed to start incremental decode\n");
352         return;
353     }
354 
355     SkCodec::Result result = partialCodec->incrementalDecode();
356     REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
357 
358     SkBitmap full;
359     full.allocPixels(info);
360     result = partialCodec->getPixels(info, full.getPixels(), full.rowBytes());
361     REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
362 
363     // Now incremental decode will fail
364     result = partialCodec->incrementalDecode();
365     REPORTER_ASSERT(r, result == SkCodec::kInvalidParameters);
366 }
367 
DEF_TEST(Codec_rewind,r)368 DEF_TEST(Codec_rewind, r) {
369     test_interleaved(r, "images/plane.png");
370     test_interleaved(r, "images/plane_interlaced.png");
371     test_interleaved(r, "images/box.gif");
372 }
373 
374 // Modified version of the giflib logo, from
375 // http://giflib.sourceforge.net/whatsinagif/bits_and_bytes.html
376 // The global color map has been replaced with a local color map.
377 static unsigned char gNoGlobalColorMap[] = {
378   // Header
379   0x47, 0x49, 0x46, 0x38, 0x39, 0x61,
380 
381   // Logical screen descriptor
382   0x0A, 0x00, 0x0A, 0x00, 0x11, 0x00, 0x00,
383 
384   // Image descriptor
385   0x2C, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x81,
386 
387   // Local color table
388   0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00,
389 
390   // Image data
391   0x02, 0x16, 0x8C, 0x2D, 0x99, 0x87, 0x2A, 0x1C, 0xDC, 0x33, 0xA0, 0x02, 0x75,
392   0xEC, 0x95, 0xFA, 0xA8, 0xDE, 0x60, 0x8C, 0x04, 0x91, 0x4C, 0x01, 0x00,
393 
394   // Trailer
395   0x3B,
396 };
397 
398 // Test that a gif file truncated before its local color map behaves as expected.
DEF_TEST(Codec_GifPreMap,r)399 DEF_TEST(Codec_GifPreMap, r) {
400     sk_sp<SkData> data = SkData::MakeWithoutCopy(gNoGlobalColorMap, sizeof(gNoGlobalColorMap));
401     std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(data));
402     if (!codec) {
403         ERRORF(r, "failed to create codec");
404         return;
405     }
406 
407     SkBitmap truth;
408     auto info = standardize_info(codec.get());
409     truth.allocPixels(info);
410 
411     auto result = codec->getPixels(info, truth.getPixels(), truth.rowBytes());
412     REPORTER_ASSERT(r, result == SkCodec::kSuccess);
413 
414     // Truncate to 23 bytes, just before the color map. This should fail to decode.
415     //
416     // See also Codec_GifTruncated2 in GifTest.cpp for this magic 23.
417     codec = SkCodec::MakeFromData(SkData::MakeWithoutCopy(gNoGlobalColorMap, 23));
418     REPORTER_ASSERT(r, codec);
419     if (codec) {
420         SkBitmap bm;
421         bm.allocPixels(info);
422         result = codec->getPixels(info, bm.getPixels(), bm.rowBytes());
423 
424         REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
425     }
426 
427     // Again, truncate to 23 bytes, this time for an incremental decode. We
428     // cannot start an incremental decode until we have more data. If we did,
429     // we would be using the wrong color table.
430     HaltingStream* stream = new HaltingStream(data, 23);
431     codec = SkCodec::MakeFromStream(std::unique_ptr<SkStream>(stream));
432     REPORTER_ASSERT(r, codec);
433     if (codec) {
434         SkBitmap bm;
435         bm.allocPixels(info);
436         result = codec->startIncrementalDecode(info, bm.getPixels(), bm.rowBytes());
437 
438         REPORTER_ASSERT(r, result == SkCodec::kSuccess);
439 
440         // Note that this is incrementalDecode, not startIncrementalDecode.
441         result = codec->incrementalDecode();
442         REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
443 
444         stream->addNewData(data->size());
445 
446         result = codec->incrementalDecode();
447         REPORTER_ASSERT(r, result == SkCodec::kSuccess);
448         compare_bitmaps(r, truth, bm);
449     }
450 }
451 
DEF_TEST(Codec_emptyIDAT,r)452 DEF_TEST(Codec_emptyIDAT, r) {
453     const char* name = "images/baby_tux.png";
454     sk_sp<SkData> file = GetResourceAsData(name);
455     if (!file) {
456         return;
457     }
458 
459     // Truncate to the beginning of the IDAT, immediately after the IDAT tag.
460     file = SkData::MakeSubset(file.get(), 0, 80);
461 
462     std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(std::move(file)));
463     if (!codec) {
464         ERRORF(r, "Failed to create a codec for %s", name);
465         return;
466     }
467 
468     SkBitmap bm;
469     const auto info = standardize_info(codec.get());
470     bm.allocPixels(info);
471 
472     const auto result = codec->getPixels(info, bm.getPixels(), bm.rowBytes());
473     REPORTER_ASSERT(r, SkCodec::kIncompleteInput == result);
474 }
475 
DEF_TEST(Codec_incomplete,r)476 DEF_TEST(Codec_incomplete, r) {
477     for (const char* name : { "images/baby_tux.png",
478                               "images/baby_tux.webp",
479                               "images/CMYK.jpg",
480                               "images/color_wheel.gif",
481                               "images/google_chrome.ico",
482                               "images/rle.bmp",
483                               "images/mandrill.wbmp",
484                               }) {
485         sk_sp<SkData> file = GetResourceAsData(name);
486         if (!file) {
487             continue;
488         }
489 
490         for (size_t len = 14; len <= file->size(); len += 5) {
491             SkCodec::Result result;
492             std::unique_ptr<SkCodec> codec(SkCodec::MakeFromStream(
493                                    std::make_unique<SkMemoryStream>(file->data(), len), &result));
494             if (codec) {
495                 if (result != SkCodec::kSuccess) {
496                     ERRORF(r, "Created an SkCodec for %s with %zu bytes, but "
497                               "reported an error %i", name, len, (int)result);
498                 }
499                 break;
500             }
501 
502             if (SkCodec::kIncompleteInput != result) {
503                 ERRORF(r, "Reported error %i for %s with %zu bytes",
504                        (int)result, name, len);
505                 break;
506             }
507         }
508     }
509 }
510