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 SkColorType gPrefColorType(kUnknown_SkColorType);
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.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 */
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, 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 */
473static 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
484static 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.
702static 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 */
719static 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 */
728static 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
741int tool_main(int argc, char** argv);
742int 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
839int main(int argc, char * const argv[]) {
840    return tool_main(argc, (char**) argv);
841}
842#endif
843