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