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 SkColorType gPrefColorType(kUnknown_SkColorType);
    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.colorType() == kN32_SkColorType) {
    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, kN32_SkColorType)) {
    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, gPrefColorType, 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  * Replaces all instances of oldChar with newChar in str.
    468  *
    469  * TODO: This function appears here and in picture_utils.[cpp|h] ;
    470  * we should add the implementation to src/core/SkString.cpp, write tests for it,
    471  * and remove it from elsewhere.
    472  */
    473 static void replace_char(SkString* str, const char oldChar, const char newChar) {
    474     if (NULL == str) {
    475         return;
    476     }
    477     for (size_t i = 0; i < str->size(); ++i) {
    478         if (oldChar == str->operator[](i)) {
    479             str->operator[](i) = newChar;
    480         }
    481     }
    482 }
    483 
    484 static void decodeFileAndWrite(const char srcPath[], const SkString* writePath) {
    485     SkBitmap bitmap;
    486     SkFILEStream stream(srcPath);
    487     if (!stream.isValid()) {
    488         gInvalidStreams.push_back().set(srcPath);
    489         return;
    490     }
    491 
    492     SkImageDecoder* codec = SkImageDecoder::Factory(&stream);
    493     if (NULL == codec) {
    494         gMissingCodecs.push_back().set(srcPath);
    495         return;
    496     }
    497 
    498     SkAutoTDelete<SkImageDecoder> ad(codec);
    499 
    500     codec->setSkipWritingZeroes(FLAGS_skip);
    501     codec->setSampleSize(FLAGS_sampleSize);
    502     stream.rewind();
    503 
    504     // Create a string representing just the filename itself, for use in json expectations.
    505     SkString basename = SkOSPath::SkBasename(srcPath);
    506     // Replace '_' with '-', so that the names can fit gm_json.py's IMAGE_FILENAME_PATTERN
    507     replace_char(&basename, '_', '-');
    508     // Replace '.' with '-', so the output filename can still retain the original file extension,
    509     // but still end up with only one '.', which denotes the actual extension of the final file.
    510     replace_char(&basename, '.', '-');
    511     const char* filename = basename.c_str();
    512 
    513     if (!codec->decode(&stream, &bitmap, gPrefColorType, SkImageDecoder::kDecodePixels_Mode)) {
    514         if (NULL != gJsonExpectations.get()) {
    515             const SkString name_config = create_json_key(filename);
    516             skiagm::Expectations jsExpectations = gJsonExpectations->get(name_config.c_str());
    517             if (jsExpectations.ignoreFailure()) {
    518                 // This is a known failure.
    519                 gKnownFailures.push_back().appendf(
    520                     "failed to decode %s, which is a known failure.", srcPath);
    521                 return;
    522             }
    523             if (jsExpectations.empty()) {
    524                 // This is a failure, but it is a new file. Mark it as missing, with
    525                 // a note that it should be marked failing.
    526                 gMissingExpectations.push_back().appendf(
    527                     "new file %s (with no expectations) FAILED to decode.", srcPath);
    528                 return;
    529             }
    530         }
    531 
    532         // If there was a failure, and either there was no expectations file, or
    533         // the expectations file listed a valid expectation, report the failure.
    534         gDecodeFailures.push_back().set(srcPath);
    535         return;
    536     }
    537 
    538     // Test decoding just the bounds. The bounds should always match.
    539     {
    540         stream.rewind();
    541         SkBitmap dim;
    542         if (!codec->decode(&stream, &dim, SkImageDecoder::kDecodeBounds_Mode)) {
    543             SkString failure = SkStringPrintf("failed to decode bounds for %s", srcPath);
    544             gDecodeFailures.push_back() = failure;
    545         } else {
    546             // Now check that the bounds match:
    547             if (dim.width() != bitmap.width() || dim.height() != bitmap.height()) {
    548                 SkString failure = SkStringPrintf("bounds do not match for %s", srcPath);
    549                 gDecodeFailures.push_back() = failure;
    550             }
    551         }
    552     }
    553 
    554     skiagm::BitmapAndDigest bitmapAndDigest(bitmap);
    555     if (compare_to_expectations_if_necessary(bitmapAndDigest.fDigest, filename, &gDecodeFailures,
    556                                              &gMissingExpectations, &gKnownFailures)) {
    557         gSuccessfulDecodes.push_back().printf("%s [%d %d]", srcPath, bitmap.width(),
    558                                               bitmap.height());
    559     } else if (!FLAGS_mismatchPath.isEmpty()) {
    560         if (write_bitmap(FLAGS_mismatchPath[0], filename, bitmapAndDigest)) {
    561             gSuccessfulDecodes.push_back().appendf("\twrote %s", filename);
    562         } else {
    563             gEncodeFailures.push_back().set(filename);
    564         }
    565     }
    566 
    567 // FIXME: This test could be run on windows/mac once we remove their dependence on
    568 // getLength. See https://code.google.com/p/skia/issues/detail?id=1570
    569 #if defined(SK_BUILD_FOR_ANDROID) || defined(SK_BUILD_FOR_UNIX)
    570     test_stream_without_length(srcPath, codec, bitmapAndDigest.fDigest);
    571 #endif
    572 
    573     if (writePath != NULL) {
    574         if (write_bitmap(writePath->c_str(), filename, bitmapAndDigest)) {
    575             gSuccessfulDecodes.push_back().appendf("\twrote %s", filename);
    576         } else {
    577             gEncodeFailures.push_back().set(filename);
    578         }
    579     }
    580 
    581     write_expectations(bitmapAndDigest, filename);
    582 
    583     if (FLAGS_testSubsetDecoding) {
    584         SkDEBUGCODE(bool couldRewind =) stream.rewind();
    585         SkASSERT(couldRewind);
    586         int width, height;
    587         // Build the tile index for decoding subsets. If the image is 1x1, skip subset
    588         // decoding since there are no smaller subsets.
    589         if (codec->buildTileIndex(&stream, &width, &height) && width > 1 && height > 1) {
    590             SkASSERT(bitmap.width() == width && bitmap.height() == height);
    591             // Call decodeSubset multiple times:
    592             SkRandom rand(0);
    593             for (int i = 0; i < 5; i++) {
    594                 SkBitmap bitmapFromDecodeSubset;
    595                 // FIXME: Come up with a more representative set of rectangles.
    596                 SkIRect rect = generate_random_rect(&rand, width, height);
    597                 SkString subsetDim = SkStringPrintf("%d_%d_%d_%d", rect.fLeft, rect.fTop,
    598                                                     rect.fRight, rect.fBottom);
    599                 if (codec->decodeSubset(&bitmapFromDecodeSubset, rect, gPrefColorType)) {
    600                     SkString subsetName = SkStringPrintf("%s-%s", filename, subsetDim.c_str());
    601                     skiagm::BitmapAndDigest subsetBitmapAndDigest(bitmapFromDecodeSubset);
    602                     if (compare_to_expectations_if_necessary(subsetBitmapAndDigest.fDigest,
    603                                                              subsetName.c_str(),
    604                                                              &gFailedSubsetDecodes,
    605                                                              &gMissingSubsetExpectations,
    606                                                              &gKnownSubsetFailures)) {
    607                         gSuccessfulSubsetDecodes.push_back().printf("Decoded subset %s from %s",
    608                                                               subsetDim.c_str(), srcPath);
    609                     } else if (!FLAGS_mismatchPath.isEmpty()) {
    610                         write_subset(FLAGS_mismatchPath[0], subsetName,
    611                                      subsetBitmapAndDigest, rect, bitmap);
    612                     }
    613 
    614                     write_expectations(subsetBitmapAndDigest, subsetName.c_str());
    615 
    616                     if (writePath != NULL) {
    617                         write_subset(writePath->c_str(), subsetName,
    618                                      subsetBitmapAndDigest, rect, bitmap);
    619                     }
    620                 } else {
    621                     gFailedSubsetDecodes.push_back().printf("Failed to decode region %s from %s",
    622                                                             subsetDim.c_str(), srcPath);
    623                 }
    624             }
    625         }
    626     }
    627 
    628     // Do not attempt to re-encode A8, since our image encoders do not support encoding to A8.
    629     if (FLAGS_reencode && bitmap.colorType() != kAlpha_8_SkColorType) {
    630         // Encode to the format the file was originally in, or PNG if the encoder for the same
    631         // format is unavailable.
    632         SkImageDecoder::Format format = codec->getFormat();
    633         if (SkImageDecoder::kUnknown_Format == format) {
    634             if (stream.rewind()) {
    635                 format = SkImageDecoder::GetStreamFormat(&stream);
    636             }
    637             if (SkImageDecoder::kUnknown_Format == format) {
    638                 const char* dot = strrchr(srcPath, '.');
    639                 if (NULL != dot) {
    640                     format = guess_format_from_suffix(dot);
    641                 }
    642                 if (SkImageDecoder::kUnknown_Format == format) {
    643                     SkDebugf("Could not determine type for '%s'\n", srcPath);
    644                     format = SkImageDecoder::kPNG_Format;
    645                 }
    646 
    647             }
    648         } else {
    649             SkASSERT(!stream.rewind() || SkImageDecoder::GetStreamFormat(&stream) == format);
    650         }
    651         SkImageEncoder::Type type = format_to_type(format);
    652         // format should never be kUnknown_Format, so type should never be kUnknown_Type.
    653         SkASSERT(type != SkImageEncoder::kUnknown_Type);
    654 
    655         SkImageEncoder* encoder = SkImageEncoder::Create(type);
    656         if (NULL == encoder) {
    657             type = SkImageEncoder::kPNG_Type;
    658             encoder = SkImageEncoder::Create(type);
    659             SkASSERT(encoder);
    660         }
    661         SkAutoTDelete<SkImageEncoder> ade(encoder);
    662         // Encode to a stream.
    663         SkDynamicMemoryWStream wStream;
    664         if (!encoder->encodeStream(&wStream, bitmap, 100)) {
    665             gEncodeFailures.push_back().printf("Failed to reencode %s to type '%s'", srcPath,
    666                                                suffix_for_type(type));
    667             return;
    668         }
    669 
    670         SkAutoTUnref<SkData> data(wStream.copyToData());
    671         if (writePath != NULL && type != SkImageEncoder::kPNG_Type) {
    672             // Write the encoded data to a file. Do not write to PNG, which was already written.
    673             SkString outPath;
    674             make_outname(&outPath, writePath->c_str(), filename, suffix_for_type(type));
    675             SkFILEWStream file(outPath.c_str());
    676             if(file.write(data->data(), data->size())) {
    677                 gSuccessfulDecodes.push_back().appendf("\twrote %s", outPath.c_str());
    678             } else {
    679                 gEncodeFailures.push_back().printf("Failed to write %s", outPath.c_str());
    680             }
    681         }
    682         // Ensure that the reencoded data can still be decoded.
    683         SkMemoryStream memStream(data);
    684         SkBitmap redecodedBitmap;
    685         SkImageDecoder::Format formatOnSecondDecode;
    686         if (SkImageDecoder::DecodeStream(&memStream, &redecodedBitmap, gPrefColorType,
    687                                          SkImageDecoder::kDecodePixels_Mode,
    688                                          &formatOnSecondDecode)) {
    689             SkASSERT(format_to_type(formatOnSecondDecode) == type);
    690         } else {
    691             gDecodeFailures.push_back().printf("Failed to redecode %s after reencoding to '%s'",
    692                                                srcPath, suffix_for_type(type));
    693         }
    694     }
    695 }
    696 
    697 ///////////////////////////////////////////////////////////////////////////////
    698 
    699 // If strings is not empty, print title, followed by each string on its own line starting
    700 // with a tab.
    701 // @return bool True if strings had at least one entry.
    702 static bool print_strings(const char* title, const SkTArray<SkString, false>& strings) {
    703     if (strings.count() > 0) {
    704         SkDebugf("%s:\n", title);
    705         for (int i = 0; i < strings.count(); i++) {
    706             SkDebugf("\t%s\n", strings[i].c_str());
    707         }
    708         SkDebugf("\n");
    709         return true;
    710     }
    711     return false;
    712 }
    713 
    714 /**
    715  *  If directory is non null and does not end with a path separator, append one.
    716  *  @param directory SkString representing the path to a directory. If the last character is not a
    717  *      path separator (specific to the current OS), append one.
    718  */
    719 static void append_path_separator_if_necessary(SkString* directory) {
    720     if (directory != NULL && directory->c_str()[directory->size() - 1] != SkPATH_SEPARATOR) {
    721         directory->appendf("%c", SkPATH_SEPARATOR);
    722     }
    723 }
    724 
    725 /**
    726  *  Return true if the filename represents an image.
    727  */
    728 static bool is_image_file(const char* filename) {
    729     const char* gImageExtensions[] = {
    730         ".png", ".PNG", ".jpg", ".JPG", ".jpeg", ".JPEG", ".bmp", ".BMP",
    731         ".webp", ".WEBP", ".ico", ".ICO", ".wbmp", ".WBMP", ".gif", ".GIF"
    732     };
    733     for (size_t i = 0; i < SK_ARRAY_COUNT(gImageExtensions); ++i) {
    734         if (SkStrEndsWith(filename, gImageExtensions[i])) {
    735             return true;
    736         }
    737     }
    738     return false;
    739 }
    740 
    741 int tool_main(int argc, char** argv);
    742 int tool_main(int argc, char** argv) {
    743     SkCommandLineFlags::SetUsage("Decode files, and optionally write the results to files.");
    744     SkCommandLineFlags::Parse(argc, argv);
    745 
    746     if (FLAGS_readPath.count() < 1) {
    747         SkDebugf("Folder(s) or image(s) to decode are required.\n");
    748         return -1;
    749     }
    750 
    751 
    752     SkAutoGraphics ag;
    753 
    754     if (!FLAGS_readExpectationsPath.isEmpty() && sk_exists(FLAGS_readExpectationsPath[0])) {
    755         gJsonExpectations.reset(SkNEW_ARGS(skiagm::JsonExpectationsSource,
    756                                            (FLAGS_readExpectationsPath[0])));
    757     }
    758 
    759     SkString outDir;
    760     SkString* outDirPtr;
    761 
    762     if (FLAGS_writePath.count() == 1) {
    763         outDir.set(FLAGS_writePath[0]);
    764         append_path_separator_if_necessary(&outDir);
    765         outDirPtr = &outDir;
    766     } else {
    767         outDirPtr = NULL;
    768     }
    769 
    770     if (FLAGS_config.count() == 1) {
    771         // Only consider the first config specified on the command line.
    772         const char* config = FLAGS_config[0];
    773         if (0 == strcmp(config, "8888")) {
    774             gPrefColorType = kN32_SkColorType;
    775         } else if (0 == strcmp(config, "565")) {
    776             gPrefColorType = kRGB_565_SkColorType;
    777         } else if (0 == strcmp(config, "A8")) {
    778             gPrefColorType = kAlpha_8_SkColorType;
    779         } else if (0 != strcmp(config, "None")) {
    780             SkDebugf("Invalid preferred config\n");
    781             return -1;
    782         }
    783     }
    784 
    785     for (int i = 0; i < FLAGS_readPath.count(); i++) {
    786         const char* readPath = FLAGS_readPath[i];
    787         if (strlen(readPath) < 1) {
    788             break;
    789         }
    790         if (sk_isdir(readPath)) {
    791             const char* dir = readPath;
    792             SkOSFile::Iter iter(dir);
    793             SkString filename;
    794             while (iter.next(&filename)) {
    795                 if (!is_image_file(filename.c_str())) {
    796                     continue;
    797                 }
    798                 SkString fullname = SkOSPath::SkPathJoin(dir, filename.c_str());
    799                 decodeFileAndWrite(fullname.c_str(), outDirPtr);
    800             }
    801         } else if (sk_exists(readPath) && is_image_file(readPath)) {
    802             decodeFileAndWrite(readPath, outDirPtr);
    803         }
    804     }
    805 
    806     if (!FLAGS_createExpectationsPath.isEmpty()) {
    807         // Use an empty value for everything besides expectations, since the reader only cares
    808         // about the expectations.
    809         Json::Value nullValue;
    810         Json::Value root = skiagm::CreateJsonTree(gExpectationsToWrite, nullValue, nullValue,
    811                                                   nullValue, nullValue);
    812         std::string jsonStdString = root.toStyledString();
    813         SkFILEWStream stream(FLAGS_createExpectationsPath[0]);
    814         stream.write(jsonStdString.c_str(), jsonStdString.length());
    815     }
    816     // Add some space, since codecs may print warnings without newline.
    817     SkDebugf("\n\n");
    818 
    819     bool failed = print_strings("Invalid files", gInvalidStreams);
    820     failed |= print_strings("Missing codec", gMissingCodecs);
    821     failed |= print_strings("Failed to decode", gDecodeFailures);
    822     failed |= print_strings("Failed to encode", gEncodeFailures);
    823     print_strings("Decoded", gSuccessfulDecodes);
    824     print_strings("Missing expectations", gMissingExpectations);
    825 
    826     if (FLAGS_testSubsetDecoding) {
    827         failed |= print_strings("Failed subset decodes", gFailedSubsetDecodes);
    828         print_strings("Decoded subsets", gSuccessfulSubsetDecodes);
    829         print_strings("Missing subset expectations", gMissingSubsetExpectations);
    830         print_strings("Known subset failures", gKnownSubsetFailures);
    831     }
    832 
    833     print_strings("Known failures", gKnownFailures);
    834 
    835     return failed ? -1 : 0;
    836 }
    837 
    838 #if !defined SK_BUILD_FOR_IOS
    839 int main(int argc, char * const argv[]) {
    840     return tool_main(argc, (char**) argv);
    841 }
    842 #endif
    843