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