Home | History | Annotate | Download | only in tools
      1 /*
      2  * Copyright 2011 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 "gm_expectations.h"
      9 #include "SkBitmap.h"
     10 #include "SkColorPriv.h"
     11 #include "SkCommandLineFlags.h"
     12 #include "SkData.h"
     13 #include "SkForceLinking.h"
     14 #include "SkGraphics.h"
     15 #include "SkImageDecoder.h"
     16 #include "SkImageEncoder.h"
     17 #include "SkOSFile.h"
     18 #include "SkRandom.h"
     19 #include "SkStream.h"
     20 #include "SkTArray.h"
     21 #include "SkTemplates.h"
     22 
     23 __SK_FORCE_IMAGE_DECODER_LINKING;
     24 
     25 DEFINE_string(config, "None", "Preferred config to decode into. [None|8888|565|A8]");
     26 DEFINE_string(createExpectationsPath, "", "Path to write JSON expectations.");
     27 DEFINE_string(mismatchPath, "", "Folder to write mismatched images to.");
     28 DEFINE_string2(readPath, r, "", "Folder(s) and files to decode images. Required.");
     29 DEFINE_string(readExpectationsPath, "", "Path to read JSON expectations from.");
     30 DEFINE_bool(reencode, true, "Reencode the images to test encoding.");
     31 DEFINE_int32(sampleSize, 1, "Set the sampleSize for decoding.");
     32 DEFINE_bool(skip, false, "Skip writing zeroes.");
     33 DEFINE_bool(testSubsetDecoding, true, "Test decoding subsets of images.");
     34 DEFINE_bool(writeChecksumBasedFilenames, false,  "When writing out actual images, use checksum-"
     35             "based filenames, as rebaseline.py will use when downloading them from Google Storage");
     36 DEFINE_string2(writePath, w, "",  "Write rendered images into this directory.");
     37 
     38 struct Format {
     39     SkImageEncoder::Type    fType;
     40     SkImageDecoder::Format  fFormat;
     41     const char*             fSuffix;
     42 };
     43 
     44 static const Format gFormats[] = {
     45     { SkImageEncoder::kBMP_Type, SkImageDecoder::kBMP_Format, ".bmp" },
     46     { SkImageEncoder::kGIF_Type, SkImageDecoder::kGIF_Format, ".gif" },
     47     { SkImageEncoder::kICO_Type, SkImageDecoder::kICO_Format, ".ico" },
     48     { SkImageEncoder::kJPEG_Type, SkImageDecoder::kJPEG_Format, ".jpg" },
     49     { SkImageEncoder::kPNG_Type, SkImageDecoder::kPNG_Format, ".png" },
     50     { SkImageEncoder::kWBMP_Type, SkImageDecoder::kWBMP_Format, ".wbmp" },
     51     { SkImageEncoder::kWEBP_Type, SkImageDecoder::kWEBP_Format, ".webp" }
     52 };
     53 
     54 static SkImageEncoder::Type format_to_type(SkImageDecoder::Format format) {
     55     for (size_t i = 0; i < SK_ARRAY_COUNT(gFormats); i++) {
     56         if (gFormats[i].fFormat == format) {
     57             return gFormats[i].fType;
     58         }
     59     }
     60     return SkImageEncoder::kUnknown_Type;
     61 }
     62 
     63 static const char* suffix_for_type(SkImageEncoder::Type type) {
     64     for (size_t i = 0; i < SK_ARRAY_COUNT(gFormats); i++) {
     65         if (gFormats[i].fType == type) {
     66             return gFormats[i].fSuffix;
     67         }
     68     }
     69     return "";
     70 }
     71 
     72 static SkImageDecoder::Format guess_format_from_suffix(const char suffix[]) {
     73     for (size_t i = 0; i < SK_ARRAY_COUNT(gFormats); i++) {
     74         if (strcmp(suffix, gFormats[i].fSuffix) == 0) {
     75             return gFormats[i].fFormat;
     76         }
     77     }
     78     return SkImageDecoder::kUnknown_Format;
     79 }
     80 
     81 static void make_outname(SkString* dst, const char outDir[], const char src[],
     82                          const char suffix[]) {
     83     SkString basename = SkOSPath::SkBasename(src);
     84     dst->set(SkOSPath::SkPathJoin(outDir, basename.c_str()));
     85     dst->append(suffix);
     86 }
     87 
     88 // Store the names of the filenames to report later which ones failed, succeeded, and were
     89 // invalid.
     90 // FIXME: Add more arrays, for more specific types of errors, and make the output simpler.
     91 // If each array holds one type of error, the output can change from:
     92 //
     93 // Failures:
     94 //      <image> failed for such and such reason
     95 //      <image> failed for some different reason
     96 //
     97 // to:
     98 //
     99 // Such and such failures:
    100 //      <image>
    101 //
    102 // Different reason failures:
    103 //      <image>
    104 //
    105 static SkTArray<SkString, false> gInvalidStreams;
    106 static SkTArray<SkString, false> gMissingCodecs;
    107 static SkTArray<SkString, false> gDecodeFailures;
    108 static SkTArray<SkString, false> gEncodeFailures;
    109 static SkTArray<SkString, false> gSuccessfulDecodes;
    110 static SkTArray<SkString, false> gSuccessfulSubsetDecodes;
    111 static SkTArray<SkString, false> gFailedSubsetDecodes;
    112 // Files/subsets that do not have expectations. Not reported as a failure of the test so
    113 // the bots will not turn red with each new image test.
    114 static SkTArray<SkString, false> gMissingExpectations;
    115 static SkTArray<SkString, false> gMissingSubsetExpectations;
    116 // For files that are expected to fail.
    117 static SkTArray<SkString, false> gKnownFailures;
    118 static SkTArray<SkString, false> gKnownSubsetFailures;
    119 
    120 static SkBitmap::Config gPrefConfig(SkBitmap::kNo_Config);
    121 
    122 // Expections read from a file specified by readExpectationsPath. The expectations must have been
    123 // previously written using createExpectationsPath.
    124 SkAutoTUnref<skiagm::JsonExpectationsSource> gJsonExpectations;
    125 
    126 /**
    127  *  Encode the bitmap to a file, written one of two ways, depending on
    128  *  FLAGS_writeChecksumBasedFilenames. If true, the final image will be
    129  *  written to:
    130  *      outDir/hashType/src/digestValue.png
    131  *  If false, the final image will be written out to:
    132  *      outDir/src.png
    133  *  The function returns whether the file was successfully written.
    134  */
    135 static bool write_bitmap(const char outDir[], const char src[],
    136                          const skiagm::BitmapAndDigest& bitmapAndDigest) {
    137     SkString filename;
    138     if (FLAGS_writeChecksumBasedFilenames) {
    139         // First create the directory for the hashtype.
    140         const SkString hashType = bitmapAndDigest.fDigest.getHashType();
    141         const SkString hashDir = SkOSPath::SkPathJoin(outDir, hashType.c_str());
    142         if (!sk_mkdir(hashDir.c_str())) {
    143             return false;
    144         }
    145 
    146         // Now create the name of the folder specific to this image.
    147         SkString basename = SkOSPath::SkBasename(src);
    148         const SkString imageDir = SkOSPath::SkPathJoin(hashDir.c_str(), basename.c_str());
    149         if (!sk_mkdir(imageDir.c_str())) {
    150             return false;
    151         }
    152 
    153         // Name the file <digest>.png
    154         SkString checksumBasedName = bitmapAndDigest.fDigest.getDigestValue();
    155         checksumBasedName.append(".png");
    156 
    157         filename = SkOSPath::SkPathJoin(imageDir.c_str(), checksumBasedName.c_str());
    158     } else {
    159         make_outname(&filename, outDir, src, ".png");
    160     }
    161 
    162     const SkBitmap& bm = bitmapAndDigest.fBitmap;
    163     if (SkImageEncoder::EncodeFile(filename.c_str(), bm, SkImageEncoder::kPNG_Type, 100)) {
    164         return true;
    165     }
    166 
    167     if (bm.config() == SkBitmap::kARGB_8888_Config) {
    168         // First attempt at encoding failed, and the bitmap was already 8888. Making
    169         // a copy is not going to help.
    170         return false;
    171     }
    172 
    173     // Encoding failed. Copy to 8888 and try again.
    174     SkBitmap bm8888;
    175     if (!bm.copyTo(&bm8888, SkBitmap::kARGB_8888_Config)) {
    176         return false;
    177     }
    178     return SkImageEncoder::EncodeFile(filename.c_str(), bm8888, SkImageEncoder::kPNG_Type, 100);
    179 }
    180 
    181 /**
    182  *  Return a random SkIRect inside the range specified.
    183  *  @param rand Random number generator.
    184  *  @param maxX Exclusive maximum x-coordinate. SkIRect's fLeft and fRight will be
    185  *      in the range [0, maxX)
    186  *  @param maxY Exclusive maximum y-coordinate. SkIRect's fTop and fBottom will be
    187  *      in the range [0, maxY)
    188  *  @return SkIRect Non-empty, non-degenerate rectangle.
    189  */
    190 static SkIRect generate_random_rect(SkRandom* rand, int32_t maxX, int32_t maxY) {
    191     SkASSERT(maxX > 1 && maxY > 1);
    192     int32_t left = rand->nextULessThan(maxX);
    193     int32_t right = rand->nextULessThan(maxX);
    194     int32_t top = rand->nextULessThan(maxY);
    195     int32_t bottom = rand->nextULessThan(maxY);
    196     SkIRect rect = SkIRect::MakeLTRB(left, top, right, bottom);
    197     rect.sort();
    198     // Make sure rect is not empty.
    199     if (rect.fLeft == rect.fRight) {
    200         if (rect.fLeft > 0) {
    201             rect.fLeft--;
    202         } else {
    203             rect.fRight++;
    204             // This branch is only taken if 0 == rect.fRight, and
    205             // maxX must be at least 2, so it must still be in
    206             // range.
    207             SkASSERT(rect.fRight < maxX);
    208         }
    209     }
    210     if (rect.fTop == rect.fBottom) {
    211         if (rect.fTop > 0) {
    212             rect.fTop--;
    213         } else {
    214             rect.fBottom++;
    215             // Again, this must be in range.
    216             SkASSERT(rect.fBottom < maxY);
    217         }
    218     }
    219     return rect;
    220 }
    221 
    222 /**
    223  *  Return a string which includes the name of the file and the preferred config,
    224  *  as specified by "--config". The resulting string will match the pattern of
    225  *  gm_json.py's IMAGE_FILENAME_PATTERN: "filename_config.png"
    226  */
    227 static SkString create_json_key(const char* filename) {
    228     SkASSERT(FLAGS_config.count() == 1);
    229     return SkStringPrintf("%s_%s.png", filename, FLAGS_config[0]);
    230 }
    231 
    232 // Stored expectations to be written to a file if createExpectationsPath is specified.
    233 static Json::Value gExpectationsToWrite;
    234 
    235 /**
    236  *  If expectations are to be recorded, record the bitmap expectations into the global
    237  *  expectations array.
    238  *  As is the case with reading expectations, the key used will combine the filename
    239  *  parameter with the preferred config, as specified by "--config", matching the
    240  *  pattern of gm_json.py's IMAGE_FILENAME_PATTERN: "filename_config.png"
    241  */
    242 static void write_expectations(const skiagm::BitmapAndDigest& bitmapAndDigest,
    243                                const char* filename) {
    244     const SkString name_config = create_json_key(filename);
    245     if (!FLAGS_createExpectationsPath.isEmpty()) {
    246         // Creates an Expectations object, and add it to the list to write.
    247         skiagm::Expectations expectation(bitmapAndDigest);
    248         Json::Value value = expectation.asJsonValue();
    249         gExpectationsToWrite[name_config.c_str()] = value;
    250     }
    251 }
    252 
    253 /**
    254  *  If --readExpectationsPath is set, compare this bitmap to the json expectations
    255  *  provided.
    256  *
    257  *  @param digest GmResultDigest, computed from the decoded bitmap, to compare to
    258  *         the existing expectation.
    259  *  @param filename String used to find the expected value. Will be combined with the
    260  *         preferred config, as specified by "--config", to match the pattern of
    261  *         gm_json.py's IMAGE_FILENAME_PATTERN: "filename_config.png". The resulting
    262  *         key will be used to find the proper expectations.
    263  *  @param failureArray Array to add a failure message to on failure.
    264  *  @param missingArray Array to add failure message to when missing image
    265  *          expectation.
    266  *  @param ignoreArray Array to add failure message to when the image does not match
    267  *          the expectation, but this is a failure we can ignore.
    268  *  @return bool True in any of these cases:
    269  *                  - the bitmap matches the expectation.
    270  *               False in any of these cases:
    271  *                  - there is no expectations file.
    272  *                  - there is an expectations file, but no expectation for this bitmap.
    273  *                  - there is an expectation for this bitmap, but it did not match.
    274  *                  - expectation could not be computed from the bitmap.
    275  */
    276 static bool compare_to_expectations_if_necessary(const skiagm::GmResultDigest& digest,
    277                                                  const char* filename,
    278                                                  SkTArray<SkString, false>* failureArray,
    279                                                  SkTArray<SkString, false>* missingArray,
    280                                                  SkTArray<SkString, false>* ignoreArray) {
    281     // For both writing and reading, the key for this entry will include the name
    282     // of the file and the pref config, matching the pattern of gm_json.py's
    283     // IMAGE_FILENAME_PATTERN: "name_config.png"
    284     const SkString name_config = create_json_key(filename);
    285 
    286     if (!digest.isValid()) {
    287         if (failureArray != NULL) {
    288             failureArray->push_back().printf("decoded %s, but could not create a GmResultDigest.",
    289                                              filename);
    290         }
    291         return false;
    292     }
    293 
    294     if (NULL == gJsonExpectations.get()) {
    295         return false;
    296     }
    297 
    298     skiagm::Expectations jsExpectation = gJsonExpectations->get(name_config.c_str());
    299     if (jsExpectation.empty()) {
    300         if (missingArray != NULL) {
    301             missingArray->push_back().printf("decoded %s, but could not find expectation.",
    302                                              filename);
    303         }
    304         return false;
    305     }
    306 
    307     if (jsExpectation.match(digest)) {
    308         return true;
    309     }
    310 
    311     if (jsExpectation.ignoreFailure()) {
    312         ignoreArray->push_back().printf("%s does not match expectation, but this is known.",
    313                                         filename);
    314     } else if (failureArray != NULL) {
    315         failureArray->push_back().printf("decoded %s, but the result does not match "
    316                                          "expectations.",
    317                                          filename);
    318     }
    319     return false;
    320 }
    321 
    322 /**
    323  *  Helper function to write a bitmap subset to a file. Only called if subsets were created
    324  *  and a writePath was provided. Behaves differently depending on
    325  *  FLAGS_writeChecksumBasedFilenames. If true:
    326  *      Writes the image to a PNG file named according to the digest hash, as described in
    327  *      write_bitmap.
    328  *  If false:
    329  *      Creates a subdirectory called 'subsets' and writes a PNG to that directory. Also
    330  *      creates a subdirectory called 'extracted' and writes a bitmap created using
    331  *      extractSubset to a PNG in that directory. Both files will represent the same
    332  *      subrectangle and have the same name for convenient comparison. In this case, the
    333  *      digest is ignored.
    334  *
    335  *  @param writePath Parent directory to hold the folders for the PNG files to write. Must
    336  *      not be NULL.
    337  *  @param subsetName Basename of the original file, with the dimensions of the subset tacked
    338  *      on. Used to name the new file/folder.
    339  *  @param bitmapAndDigestFromDecodeSubset SkBitmap (with digest) created by
    340  *      SkImageDecoder::DecodeSubset, using rect as the area to decode.
    341  *  @param rect Rectangle of the area decoded into bitmapFromDecodeSubset. Used to call
    342  *      extractSubset on originalBitmap to create a bitmap with the same dimensions/pixels as
    343  *      bitmapFromDecodeSubset (assuming decodeSubset worked properly).
    344  *  @param originalBitmap SkBitmap decoded from the same stream as bitmapFromDecodeSubset,
    345  *      using SkImageDecoder::decode to get the entire image. Used to create a PNG file for
    346  *      comparison to the PNG created by bitmapAndDigestFromDecodeSubset's bitmap.
    347  *  @return bool Whether the function succeeded at drawing the decoded subset and the extracted
    348  *      subset to files.
    349  */
    350 static bool write_subset(const char* writePath, const SkString& subsetName,
    351                           const skiagm::BitmapAndDigest bitmapAndDigestFromDecodeSubset,
    352                           SkIRect rect, const SkBitmap& originalBitmap) {
    353     // All parameters must be valid.
    354     SkASSERT(writePath != NULL);
    355 
    356     SkString subsetPath;
    357     if (FLAGS_writeChecksumBasedFilenames) {
    358         subsetPath.set(writePath);
    359     } else {
    360         // Create a subdirectory to hold the results of decodeSubset.
    361         subsetPath = SkOSPath::SkPathJoin(writePath, "subsets");
    362         if (!sk_mkdir(subsetPath.c_str())) {
    363             gFailedSubsetDecodes.push_back().printf("Successfully decoded subset %s, but "
    364                                                     "failed to create a directory to write to.",
    365                                                      subsetName.c_str());
    366             return false;
    367         }
    368     }
    369     SkAssertResult(write_bitmap(subsetPath.c_str(), subsetName.c_str(),
    370                                 bitmapAndDigestFromDecodeSubset));
    371     gSuccessfulSubsetDecodes.push_back().printf("\twrote %s", subsetName.c_str());
    372 
    373     if (!FLAGS_writeChecksumBasedFilenames) {
    374         // FIXME: The goal of extracting the subset is for visual comparison/using skdiff/skpdiff.
    375         // Currently disabling for writeChecksumBasedFilenames since it will be trickier to
    376         // determine which files to compare.
    377 
    378         // Also use extractSubset from the original for visual comparison.
    379         // Write the result to a file in a separate subdirectory.
    380         SkBitmap extractedSubset;
    381         if (!originalBitmap.extractSubset(&extractedSubset, rect)) {
    382             gFailedSubsetDecodes.push_back().printf("Successfully decoded subset %s, but failed "
    383                                                     "to extract a similar subset for comparison.",
    384                                                     subsetName.c_str());
    385             return false;
    386         }
    387 
    388         SkString dirExtracted = SkOSPath::SkPathJoin(writePath, "extracted");
    389         if (!sk_mkdir(dirExtracted.c_str())) {
    390             gFailedSubsetDecodes.push_back().printf("Successfully decoded subset%s, but failed "
    391                                                     "to create a directory for extractSubset "
    392                                                     "comparison.",
    393                                                     subsetName.c_str());
    394             return false;
    395         }
    396 
    397         skiagm::BitmapAndDigest bitmapAndDigestFromExtractSubset(extractedSubset);
    398         SkAssertResult(write_bitmap(dirExtracted.c_str(), subsetName.c_str(),
    399                                     bitmapAndDigestFromExtractSubset));
    400     }
    401     return true;
    402 }
    403 
    404 // FIXME: This test could be run on windows/mac once we remove their dependence on
    405 // getLength. See https://code.google.com/p/skia/issues/detail?id=1570
    406 #if defined(SK_BUILD_FOR_ANDROID) || defined(SK_BUILD_FOR_UNIX)
    407 
    408 /**
    409  *  Dummy class for testing to ensure that a stream without a length decodes the same
    410  *  as a stream with a length.
    411  */
    412 class FILEStreamWithoutLength : public SkFILEStream {
    413 public:
    414     FILEStreamWithoutLength(const char path[])
    415     : INHERITED(path) {}
    416 
    417     virtual bool hasLength() const SK_OVERRIDE {
    418         return false;
    419     }
    420 
    421 private:
    422     typedef SkFILEStream INHERITED;
    423 };
    424 
    425 /**
    426  *  Test that decoding a stream which reports to not have a length still results in the
    427  *  same image as if it did report to have a length. Assumes that codec was used to
    428  *  successfully decode the file using SkFILEStream.
    429  *  @param srcPath The path to the file, for recreating the length-less stream.
    430  *  @param codec The SkImageDecoder originally used to decode srcPath, which will be used
    431  *      again to decode the length-less stream.
    432  *  @param digest GmResultDigest computed from decoding the stream the first time.
    433  *      Decoding the length-less stream is expected to result in a matching digest.
    434  */
    435 static void test_stream_without_length(const char srcPath[], SkImageDecoder* codec,
    436                                        const skiagm::GmResultDigest& digest) {
    437     if (!digest.isValid()) {
    438         // An error was already reported.
    439         return;
    440     }
    441     SkASSERT(srcPath);
    442     SkASSERT(codec);
    443     FILEStreamWithoutLength stream(srcPath);
    444     // This will only be called after a successful decode. Creating a stream from the same
    445     // path should never fail.
    446     SkASSERT(stream.isValid());
    447     SkBitmap bm;
    448     if (!codec->decode(&stream, &bm, gPrefConfig, SkImageDecoder::kDecodePixels_Mode)) {
    449         gDecodeFailures.push_back().appendf("Without using getLength, %s failed to decode\n",
    450                                             srcPath);
    451         return;
    452     }
    453     skiagm::GmResultDigest lengthLessDigest(bm);
    454     if (!lengthLessDigest.isValid()) {
    455         gDecodeFailures.push_back().appendf("Without using getLength, %s failed to build "
    456                                             "a digest\n", srcPath);
    457         return;
    458     }
    459     if (!lengthLessDigest.equals(digest)) {
    460         gDecodeFailures.push_back().appendf("Without using getLength, %s did not match digest "
    461                                             "that uses getLength\n", srcPath);
    462     }
    463 }
    464 #endif // defined(SK_BUILD_FOR_ANDROID) || defined(SK_BUILD_FOR_UNIX)
    465 
    466 /**
    467  *  Replace all instances of oldChar with newChar in str.
    468  *  TODO: Add this function to SkString and write tests for it.
    469  */
    470 static void replace_char(SkString* str, const char oldChar, const char newChar) {
    471     if (NULL == str) {
    472         return;
    473     }
    474     for (size_t i = 0; i < str->size(); ++i) {
    475         if (oldChar == str->operator[](i)) {
    476             str->operator[](i) = newChar;
    477         }
    478     }
    479 }
    480 
    481 static void decodeFileAndWrite(const char srcPath[], const SkString* writePath) {
    482     SkBitmap bitmap;
    483     SkFILEStream stream(srcPath);
    484     if (!stream.isValid()) {
    485         gInvalidStreams.push_back().set(srcPath);
    486         return;
    487     }
    488 
    489     SkImageDecoder* codec = SkImageDecoder::Factory(&stream);
    490     if (NULL == codec) {
    491         gMissingCodecs.push_back().set(srcPath);
    492         return;
    493     }
    494 
    495     SkAutoTDelete<SkImageDecoder> ad(codec);
    496 
    497     codec->setSkipWritingZeroes(FLAGS_skip);
    498     codec->setSampleSize(FLAGS_sampleSize);
    499     stream.rewind();
    500 
    501     // Create a string representing just the filename itself, for use in json expectations.
    502     SkString basename = SkOSPath::SkBasename(srcPath);
    503     // Replace '_' with '-', so that the names can fit gm_json.py's IMAGE_FILENAME_PATTERN
    504     replace_char(&basename, '_', '-');
    505     // Replace '.' with '-', so the output filename can still retain the original file extension,
    506     // but still end up with only one '.', which denotes the actual extension of the final file.
    507     replace_char(&basename, '.', '-');
    508     const char* filename = basename.c_str();
    509 
    510     if (!codec->decode(&stream, &bitmap, gPrefConfig,
    511                        SkImageDecoder::kDecodePixels_Mode)) {
    512         if (NULL != gJsonExpectations.get()) {
    513             const SkString name_config = create_json_key(filename);
    514             skiagm::Expectations jsExpectations = gJsonExpectations->get(name_config.c_str());
    515             if (jsExpectations.ignoreFailure()) {
    516                 // This is a known failure.
    517                 gKnownFailures.push_back().appendf(
    518                     "failed to decode %s, which is a known failure.", srcPath);
    519                 return;
    520             }
    521             if (jsExpectations.empty()) {
    522                 // This is a failure, but it is a new file. Mark it as missing, with
    523                 // a note that it should be marked failing.
    524                 gMissingExpectations.push_back().appendf(
    525                     "new file %s (with no expectations) FAILED to decode.", srcPath);
    526                 return;
    527             }
    528         }
    529 
    530         // If there was a failure, and either there was no expectations file, or
    531         // the expectations file listed a valid expectation, report the failure.
    532         gDecodeFailures.push_back().set(srcPath);
    533         return;
    534     }
    535 
    536     // Test decoding just the bounds. The bounds should always match.
    537     {
    538         stream.rewind();
    539         SkBitmap dim;
    540         if (!codec->decode(&stream, &dim, SkImageDecoder::kDecodeBounds_Mode)) {
    541             SkString failure = SkStringPrintf("failed to decode bounds for %s", srcPath);
    542             gDecodeFailures.push_back() = failure;
    543         } else {
    544             // Now check that the bounds match:
    545             if (dim.width() != bitmap.width() || dim.height() != bitmap.height()) {
    546                 SkString failure = SkStringPrintf("bounds do not match for %s", srcPath);
    547                 gDecodeFailures.push_back() = failure;
    548             }
    549         }
    550     }
    551 
    552     skiagm::BitmapAndDigest bitmapAndDigest(bitmap);
    553     if (compare_to_expectations_if_necessary(bitmapAndDigest.fDigest, filename, &gDecodeFailures,
    554                                              &gMissingExpectations, &gKnownFailures)) {
    555         gSuccessfulDecodes.push_back().printf("%s [%d %d]", srcPath, bitmap.width(),
    556                                               bitmap.height());
    557     } else if (!FLAGS_mismatchPath.isEmpty()) {
    558         if (write_bitmap(FLAGS_mismatchPath[0], filename, bitmapAndDigest)) {
    559             gSuccessfulDecodes.push_back().appendf("\twrote %s", filename);
    560         } else {
    561             gEncodeFailures.push_back().set(filename);
    562         }
    563     }
    564 
    565 // FIXME: This test could be run on windows/mac once we remove their dependence on
    566 // getLength. See https://code.google.com/p/skia/issues/detail?id=1570
    567 #if defined(SK_BUILD_FOR_ANDROID) || defined(SK_BUILD_FOR_UNIX)
    568     test_stream_without_length(srcPath, codec, bitmapAndDigest.fDigest);
    569 #endif
    570 
    571     if (writePath != NULL) {
    572         if (write_bitmap(writePath->c_str(), filename, bitmapAndDigest)) {
    573             gSuccessfulDecodes.push_back().appendf("\twrote %s", filename);
    574         } else {
    575             gEncodeFailures.push_back().set(filename);
    576         }
    577     }
    578 
    579     write_expectations(bitmapAndDigest, filename);
    580 
    581     if (FLAGS_testSubsetDecoding) {
    582         SkDEBUGCODE(bool couldRewind =) stream.rewind();
    583         SkASSERT(couldRewind);
    584         int width, height;
    585         // Build the tile index for decoding subsets. If the image is 1x1, skip subset
    586         // decoding since there are no smaller subsets.
    587         if (codec->buildTileIndex(&stream, &width, &height) && width > 1 && height > 1) {
    588             SkASSERT(bitmap.width() == width && bitmap.height() == height);
    589             // Call decodeSubset multiple times:
    590             SkRandom rand(0);
    591             for (int i = 0; i < 5; i++) {
    592                 SkBitmap bitmapFromDecodeSubset;
    593                 // FIXME: Come up with a more representative set of rectangles.
    594                 SkIRect rect = generate_random_rect(&rand, width, height);
    595                 SkString subsetDim = SkStringPrintf("[%d,%d,%d,%d]", rect.fLeft, rect.fTop,
    596                                                     rect.fRight, rect.fBottom);
    597                 if (codec->decodeSubset(&bitmapFromDecodeSubset, rect, gPrefConfig)) {
    598                     SkString subsetName = SkStringPrintf("%s-%s", filename, subsetDim.c_str());
    599                     skiagm::BitmapAndDigest subsetBitmapAndDigest(bitmapFromDecodeSubset);
    600                     if (compare_to_expectations_if_necessary(subsetBitmapAndDigest.fDigest,
    601                                                              subsetName.c_str(),
    602                                                              &gFailedSubsetDecodes,
    603                                                              &gMissingSubsetExpectations,
    604                                                              &gKnownSubsetFailures)) {
    605                         gSuccessfulSubsetDecodes.push_back().printf("Decoded subset %s from %s",
    606                                                               subsetDim.c_str(), srcPath);
    607                     } else if (!FLAGS_mismatchPath.isEmpty()) {
    608                         write_subset(FLAGS_mismatchPath[0], subsetName,
    609                                      subsetBitmapAndDigest, rect, bitmap);
    610                     }
    611 
    612                     write_expectations(subsetBitmapAndDigest, subsetName.c_str());
    613 
    614                     if (writePath != NULL) {
    615                         write_subset(writePath->c_str(), subsetName,
    616                                      subsetBitmapAndDigest, rect, bitmap);
    617                     }
    618                 } else {
    619                     gFailedSubsetDecodes.push_back().printf("Failed to decode region %s from %s",
    620                                                             subsetDim.c_str(), srcPath);
    621                 }
    622             }
    623         }
    624     }
    625 
    626     // Do not attempt to re-encode A8, since our image encoders do not support encoding to A8.
    627     if (FLAGS_reencode && bitmap.config() != SkBitmap::kA8_Config) {
    628         // Encode to the format the file was originally in, or PNG if the encoder for the same
    629         // format is unavailable.
    630         SkImageDecoder::Format format = codec->getFormat();
    631         if (SkImageDecoder::kUnknown_Format == format) {
    632             if (stream.rewind()) {
    633                 format = SkImageDecoder::GetStreamFormat(&stream);
    634             }
    635             if (SkImageDecoder::kUnknown_Format == format) {
    636                 const char* dot = strrchr(srcPath, '.');
    637                 if (NULL != dot) {
    638                     format = guess_format_from_suffix(dot);
    639                 }
    640                 if (SkImageDecoder::kUnknown_Format == format) {
    641                     SkDebugf("Could not determine type for '%s'\n", srcPath);
    642                     format = SkImageDecoder::kPNG_Format;
    643                 }
    644 
    645             }
    646         } else {
    647             SkASSERT(!stream.rewind() || SkImageDecoder::GetStreamFormat(&stream) == format);
    648         }
    649         SkImageEncoder::Type type = format_to_type(format);
    650         // format should never be kUnknown_Format, so type should never be kUnknown_Type.
    651         SkASSERT(type != SkImageEncoder::kUnknown_Type);
    652 
    653         SkImageEncoder* encoder = SkImageEncoder::Create(type);
    654         if (NULL == encoder) {
    655             type = SkImageEncoder::kPNG_Type;
    656             encoder = SkImageEncoder::Create(type);
    657             SkASSERT(encoder);
    658         }
    659         SkAutoTDelete<SkImageEncoder> ade(encoder);
    660         // Encode to a stream.
    661         SkDynamicMemoryWStream wStream;
    662         if (!encoder->encodeStream(&wStream, bitmap, 100)) {
    663             gEncodeFailures.push_back().printf("Failed to reencode %s to type '%s'", srcPath,
    664                                                suffix_for_type(type));
    665             return;
    666         }
    667 
    668         SkAutoTUnref<SkData> data(wStream.copyToData());
    669         if (writePath != NULL && type != SkImageEncoder::kPNG_Type) {
    670             // Write the encoded data to a file. Do not write to PNG, which was already written.
    671             SkString outPath;
    672             make_outname(&outPath, writePath->c_str(), filename, suffix_for_type(type));
    673             SkFILEWStream file(outPath.c_str());
    674             if(file.write(data->data(), data->size())) {
    675                 gSuccessfulDecodes.push_back().appendf("\twrote %s", outPath.c_str());
    676             } else {
    677                 gEncodeFailures.push_back().printf("Failed to write %s", outPath.c_str());
    678             }
    679         }
    680         // Ensure that the reencoded data can still be decoded.
    681         SkMemoryStream memStream(data);
    682         SkBitmap redecodedBitmap;
    683         SkImageDecoder::Format formatOnSecondDecode;
    684         if (SkImageDecoder::DecodeStream(&memStream, &redecodedBitmap, gPrefConfig,
    685                                           SkImageDecoder::kDecodePixels_Mode,
    686                                           &formatOnSecondDecode)) {
    687             SkASSERT(format_to_type(formatOnSecondDecode) == type);
    688         } else {
    689             gDecodeFailures.push_back().printf("Failed to redecode %s after reencoding to '%s'",
    690                                                srcPath, suffix_for_type(type));
    691         }
    692     }
    693 }
    694 
    695 ///////////////////////////////////////////////////////////////////////////////
    696 
    697 // If strings is not empty, print title, followed by each string on its own line starting
    698 // with a tab.
    699 // @return bool True if strings had at least one entry.
    700 static bool print_strings(const char* title, const SkTArray<SkString, false>& strings) {
    701     if (strings.count() > 0) {
    702         SkDebugf("%s:\n", title);
    703         for (int i = 0; i < strings.count(); i++) {
    704             SkDebugf("\t%s\n", strings[i].c_str());
    705         }
    706         SkDebugf("\n");
    707         return true;
    708     }
    709     return false;
    710 }
    711 
    712 /**
    713  *  If directory is non null and does not end with a path separator, append one.
    714  *  @param directory SkString representing the path to a directory. If the last character is not a
    715  *      path separator (specific to the current OS), append one.
    716  */
    717 static void append_path_separator_if_necessary(SkString* directory) {
    718     if (directory != NULL && directory->c_str()[directory->size() - 1] != SkPATH_SEPARATOR) {
    719         directory->appendf("%c", SkPATH_SEPARATOR);
    720     }
    721 }
    722 
    723 /**
    724  *  Return true if the filename represents an image.
    725  */
    726 static bool is_image_file(const char* filename) {
    727     const char* gImageExtensions[] = {
    728         ".png", ".PNG", ".jpg", ".JPG", ".jpeg", ".JPEG", ".bmp", ".BMP",
    729         ".webp", ".WEBP", ".ico", ".ICO", ".wbmp", ".WBMP", ".gif", ".GIF"
    730     };
    731     for (size_t i = 0; i < SK_ARRAY_COUNT(gImageExtensions); ++i) {
    732         if (SkStrEndsWith(filename, gImageExtensions[i])) {
    733             return true;
    734         }
    735     }
    736     return false;
    737 }
    738 
    739 int tool_main(int argc, char** argv);
    740 int tool_main(int argc, char** argv) {
    741     SkCommandLineFlags::SetUsage("Decode files, and optionally write the results to files.");
    742     SkCommandLineFlags::Parse(argc, argv);
    743 
    744     if (FLAGS_readPath.count() < 1) {
    745         SkDebugf("Folder(s) or image(s) to decode are required.\n");
    746         return -1;
    747     }
    748 
    749 
    750     SkAutoGraphics ag;
    751 
    752     if (!FLAGS_readExpectationsPath.isEmpty() && sk_exists(FLAGS_readExpectationsPath[0])) {
    753         gJsonExpectations.reset(SkNEW_ARGS(skiagm::JsonExpectationsSource,
    754                                            (FLAGS_readExpectationsPath[0])));
    755     }
    756 
    757     SkString outDir;
    758     SkString* outDirPtr;
    759 
    760     if (FLAGS_writePath.count() == 1) {
    761         outDir.set(FLAGS_writePath[0]);
    762         append_path_separator_if_necessary(&outDir);
    763         outDirPtr = &outDir;
    764     } else {
    765         outDirPtr = NULL;
    766     }
    767 
    768     if (FLAGS_config.count() == 1) {
    769         // Only consider the first config specified on the command line.
    770         const char* config = FLAGS_config[0];
    771         if (0 == strcmp(config, "8888")) {
    772             gPrefConfig = SkBitmap::kARGB_8888_Config;
    773         } else if (0 == strcmp(config, "565")) {
    774             gPrefConfig = SkBitmap::kRGB_565_Config;
    775         } else if (0 == strcmp(config, "A8")) {
    776             gPrefConfig = SkBitmap::kA8_Config;
    777         } else if (0 != strcmp(config, "None")) {
    778             SkDebugf("Invalid preferred config\n");
    779             return -1;
    780         }
    781     }
    782 
    783     for (int i = 0; i < FLAGS_readPath.count(); i++) {
    784         const char* readPath = FLAGS_readPath[i];
    785         if (strlen(readPath) < 1) {
    786             break;
    787         }
    788         if (sk_isdir(readPath)) {
    789             const char* dir = readPath;
    790             SkOSFile::Iter iter(dir);
    791             SkString filename;
    792             while (iter.next(&filename)) {
    793                 if (!is_image_file(filename.c_str())) {
    794                     continue;
    795                 }
    796                 SkString fullname = SkOSPath::SkPathJoin(dir, filename.c_str());
    797                 decodeFileAndWrite(fullname.c_str(), outDirPtr);
    798             }
    799         } else if (sk_exists(readPath) && is_image_file(readPath)) {
    800             decodeFileAndWrite(readPath, outDirPtr);
    801         }
    802     }
    803 
    804     if (!FLAGS_createExpectationsPath.isEmpty()) {
    805         // Use an empty value for everything besides expectations, since the reader only cares
    806         // about the expectations.
    807         Json::Value nullValue;
    808         Json::Value root = skiagm::CreateJsonTree(gExpectationsToWrite, nullValue, nullValue,
    809                                                   nullValue, nullValue);
    810         std::string jsonStdString = root.toStyledString();
    811         SkFILEWStream stream(FLAGS_createExpectationsPath[0]);
    812         stream.write(jsonStdString.c_str(), jsonStdString.length());
    813     }
    814     // Add some space, since codecs may print warnings without newline.
    815     SkDebugf("\n\n");
    816 
    817     bool failed = print_strings("Invalid files", gInvalidStreams);
    818     failed |= print_strings("Missing codec", gMissingCodecs);
    819     failed |= print_strings("Failed to decode", gDecodeFailures);
    820     failed |= print_strings("Failed to encode", gEncodeFailures);
    821     print_strings("Decoded", gSuccessfulDecodes);
    822     print_strings("Missing expectations", gMissingExpectations);
    823 
    824     if (FLAGS_testSubsetDecoding) {
    825         failed |= print_strings("Failed subset decodes", gFailedSubsetDecodes);
    826         print_strings("Decoded subsets", gSuccessfulSubsetDecodes);
    827         print_strings("Missing subset expectations", gMissingSubsetExpectations);
    828         print_strings("Known subset failures", gKnownSubsetFailures);
    829     }
    830 
    831     print_strings("Known failures", gKnownFailures);
    832 
    833     return failed ? -1 : 0;
    834 }
    835 
    836 #if !defined SK_BUILD_FOR_IOS
    837 int main(int argc, char * const argv[]) {
    838     return tool_main(argc, (char**) argv);
    839 }
    840 #endif
    841