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
25DEFINE_string(config, "None", "Preferred config to decode into. [None|8888|565|A8]");
26DEFINE_string(createExpectationsPath, "", "Path to write JSON expectations.");
27DEFINE_string(mismatchPath, "", "Folder to write mismatched images to.");
28DEFINE_string2(readPath, r, "", "Folder(s) and files to decode images. Required.");
29DEFINE_string(readExpectationsPath, "", "Path to read JSON expectations from.");
30DEFINE_bool(reencode, true, "Reencode the images to test encoding.");
31DEFINE_int32(sampleSize, 1, "Set the sampleSize for decoding.");
32DEFINE_bool(skip, false, "Skip writing zeroes.");
33DEFINE_bool(testSubsetDecoding, true, "Test decoding subsets of images.");
34DEFINE_bool(writeChecksumBasedFilenames, false,  "When writing out actual images, use checksum-"
35            "based filenames, as rebaseline.py will use when downloading them from Google Storage");
36DEFINE_string2(writePath, w, "",  "Write rendered images into this directory.");
37
38struct Format {
39    SkImageEncoder::Type    fType;
40    SkImageDecoder::Format  fFormat;
41    const char*             fSuffix;
42};
43
44static 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
54static 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
63static 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
72static 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
81static 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//
105static SkTArray<SkString, false> gInvalidStreams;
106static SkTArray<SkString, false> gMissingCodecs;
107static SkTArray<SkString, false> gDecodeFailures;
108static SkTArray<SkString, false> gEncodeFailures;
109static SkTArray<SkString, false> gSuccessfulDecodes;
110static SkTArray<SkString, false> gSuccessfulSubsetDecodes;
111static 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.
114static SkTArray<SkString, false> gMissingExpectations;
115static SkTArray<SkString, false> gMissingSubsetExpectations;
116// For files that are expected to fail.
117static SkTArray<SkString, false> gKnownFailures;
118static SkTArray<SkString, false> gKnownSubsetFailures;
119
120static 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.
124SkAutoTUnref<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 */
135static 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 */
190static 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 */
227static 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.
233static 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 */
242static 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 */
276static 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 */
350static 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 */
412class FILEStreamWithoutLength : public SkFILEStream {
413public:
414    FILEStreamWithoutLength(const char path[])
415    : INHERITED(path) {}
416
417    virtual bool hasLength() const SK_OVERRIDE {
418        return false;
419    }
420
421private:
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 */
435static 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 */
470static 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
481static 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.
700static 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 */
717static 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 */
726static 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
739int tool_main(int argc, char** argv);
740int 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
837int main(int argc, char * const argv[]) {
838    return tool_main(argc, (char**) argv);
839}
840#endif
841