Chromium Code Reviews| Index: tools/skimage_main.cpp |
| diff --git a/tools/skimage_main.cpp b/tools/skimage_main.cpp |
| index 5b8f09548535069b5f8c347cb12c1b24992bbd82..0bb81c366f6f4dc5abeed76197b2ae0f54198ae9 100644 |
| --- a/tools/skimage_main.cpp |
| +++ b/tools/skimage_main.cpp |
| @@ -31,6 +31,8 @@ DEFINE_bool(reencode, true, "Reencode the images to test encoding."); |
| DEFINE_int32(sampleSize, 1, "Set the sampleSize for decoding."); |
| DEFINE_bool(skip, false, "Skip writing zeroes."); |
| DEFINE_bool(testSubsetDecoding, true, "Test decoding subsets of images."); |
| +DEFINE_bool(writeChecksumBasedFilenames, false, "When writing out actual images, use checksum-" |
| + "based filenames, as rebaseline.py will use when downloading them from Google Storage"); |
| DEFINE_string2(writePath, w, "", "Write rendered images into this directory."); |
| struct Format { |
| @@ -85,7 +87,11 @@ static void make_outname(SkString* dst, const char outDir[], const char src[], |
| const char* dot = strrchr(cstyleDst, '.'); |
| if (dot != NULL) { |
| int32_t index = SkToS32(dot - cstyleDst); |
| - dst->remove(index, dst->size() - index); |
| + const size_t amountToRemove = dst->size() - index; |
| + // Only remove if this is a 4-5 character suffix (including '.'). |
|
epoger
2013/10/17 15:54:13
Why this "magic" constraint on the length of the e
scroggo
2013/10/18 21:28:03
Here is the motivation: It is used to create the f
epoger
2013/10/21 15:13:39
Thank you for:
1. removing the magic
2. the cogent
|
| + if (amountToRemove == 4 || amountToRemove == 5) { |
| + dst->remove(index, amountToRemove); |
| + } |
| } |
| dst->append(suffix); |
| } |
| @@ -129,8 +135,44 @@ static SkBitmap::Config gPrefConfig(SkBitmap::kNo_Config); |
| // previously written using createExpectationsPath. |
| SkAutoTUnref<skiagm::JsonExpectationsSource> gJsonExpectations; |
| -static bool write_bitmap(const char outName[], const SkBitmap& bm) { |
| - if (SkImageEncoder::EncodeFile(outName, bm, SkImageEncoder::kPNG_Type, 100)) { |
| +/** |
| + * Encode the bitmap to a file, written one of two ways, depending on |
| + * FLAGS_writeChecksumBasedFilenames. If true, the final image will be |
|
epoger
2013/10/17 15:54:13
Maybe make writeChecksumBasedFilenames a parameter
epoger
2013/10/21 15:13:39
I still think it would be better for this to be a
|
| + * written to: |
| + * outDir/hashType/src/digestValue.png |
| + * If false, the final image will be written out to: |
| + * outDir/src.png |
| + * The function returns whether the file was successfully written. |
| + */ |
| +static bool write_bitmap(const char outDir[], const char src[], |
| + const skiagm::BitmapAndDigest& bitmapAndDigest) { |
| + SkString filename; |
| + if (FLAGS_writeChecksumBasedFilenames) { |
| + // First create the directory for the hashtype. |
| + const SkString hashType = bitmapAndDigest.fDigest.getHashType(); |
| + const SkString hashDir = SkOSPath::SkPathJoin(outDir, hashType.c_str()); |
| + if (!sk_mkdir(hashDir.c_str())) { |
| + return false; |
| + } |
| + |
| + // Now create the name of the folder specific to this image. |
| + SkString basename = SkOSPath::SkBasename(src); |
| + const SkString imageDir = SkOSPath::SkPathJoin(hashDir.c_str(), basename.c_str()); |
| + if (!sk_mkdir(imageDir.c_str())) { |
| + return false; |
| + } |
| + |
| + // Name the file <digest>.png |
| + SkString checksumBasedName = bitmapAndDigest.fDigest.getDigestValue(); |
| + checksumBasedName.append(".png"); |
| + |
| + filename = SkOSPath::SkPathJoin(imageDir.c_str(), checksumBasedName.c_str()); |
| + } else { |
| + make_outname(&filename, outDir, src, ".png"); |
| + } |
| + |
| + const SkBitmap& bm = bitmapAndDigest.fBitmap; |
| + if (SkImageEncoder::EncodeFile(filename.c_str(), bm, SkImageEncoder::kPNG_Type, 100)) { |
| return true; |
| } |
| @@ -145,7 +187,7 @@ static bool write_bitmap(const char outName[], const SkBitmap& bm) { |
| if (!bm.copyTo(&bm8888, SkBitmap::kARGB_8888_Config)) { |
| return false; |
| } |
| - return SkImageEncoder::EncodeFile(outName, bm8888, SkImageEncoder::kPNG_Type, 100); |
| + return SkImageEncoder::EncodeFile(filename.c_str(), bm8888, SkImageEncoder::kPNG_Type, 100); |
| } |
| /** |
| @@ -189,27 +231,30 @@ static SkIRect generate_random_rect(SkRandom* rand, int32_t maxX, int32_t maxY) |
| return rect; |
| } |
| -// Stored expectations to be written to a file if createExpectationsPath is specified. |
| -static Json::Value gExpectationsToWrite; |
| - |
| /** |
| - * If expectations are to be recorded, record the bitmap expectations into global |
| - * expectations array. |
| + * Return a string which includes the name of the file and the pref config, |
|
epoger
2013/10/17 15:54:13
what does "pref" mean? ("prefix"? "preference"?
scroggo
2013/10/18 21:28:03
I've clarified the comment.
|
| + * matching the pattern of gm_json.py's IMAGE_FILENAME_PATTERN: "name_config.png" |
| */ |
| -static void write_expectations(const SkBitmap& bitmap, const char* filename) { |
| - if (!FLAGS_createExpectationsPath.isEmpty()) { |
| - // Creates an Expectations object, and add it to the list to write. |
| - skiagm::Expectations expectation(bitmap); |
| - Json::Value value = expectation.asJsonValue(); |
| - gExpectationsToWrite[filename] = value; |
| - } |
| +static SkString create_json_key(const char* filename) { |
| + SkASSERT(FLAGS_config.count() == 1); |
| + return SkStringPrintf("%s_%s.png", filename, FLAGS_config[0]); |
| } |
| +// Stored expectations to be written to a file if createExpectationsPath is specified. |
| +static Json::Value gExpectationsToWrite; |
| + |
| /** |
| - * Compare against an expectation for this filename, if there is one. |
| - * @param digest GmResultDigest, computed from the decoded bitmap, to compare to the |
| - * expectation. |
| - * @param filename String used to find the expected value. |
| + * This function performs two purposes: |
| + * 1) If --createExpectationsPath is set, record the expectations to the global |
| + * expectations object, for future writing to a file, which will later be |
|
epoger
2013/10/17 15:54:13
I don't think this needs to be fixed within this C
scroggo
2013/10/18 21:28:03
I actually like using globals in this case. If I w
epoger
2013/10/21 15:13:39
Fair enough. Personally, I would prefer member va
|
| + * used for comparisons. |
| + * 2) If --readExpectationsPath is set, compare this bitmap to the json expectations |
| + * provided. This portion determines the return value, as described below. |
| + * The two purposes share a function so they can share filenames and expectations. |
|
epoger
2013/10/17 15:54:13
IMHO it would be clearer for this to remain as two
scroggo
2013/10/18 21:28:03
Done.
|
| + * @param bitmapAndDigest BitmapAndDigest, computed from the decoded bitmap, to both |
|
epoger
2013/10/17 15:54:13
add newline before first param
scroggo
2013/10/18 21:28:03
Done.
|
| + * write and compare to the existing expectation. |
| + * @param filename String used to record the new expectation and to find the existing |
| + * expectation. |
| * @param failureArray Array to add a failure message to on failure. |
| * @param missingArray Array to add failure message to when missing image |
| * expectation. |
| @@ -223,11 +268,26 @@ static void write_expectations(const SkBitmap& bitmap, const char* filename) { |
| * - there is an expectation for this bitmap, but it did not match. |
| * - expectation could not be computed from the bitmap. |
| */ |
| -static bool compare_to_expectations_if_necessary(const skiagm::GmResultDigest& digest, |
| - const char* filename, |
| - SkTArray<SkString, false>* failureArray, |
| - SkTArray<SkString, false>* missingArray, |
| - SkTArray<SkString, false>* ignoreArray) { |
| +static bool write_and_compare_expectations(const skiagm::BitmapAndDigest& bitmapAndDigest, |
| + const char* filename, |
| + SkTArray<SkString, false>* failureArray, |
| + SkTArray<SkString, false>* missingArray, |
| + SkTArray<SkString, false>* ignoreArray) { |
| + // For both writing and reading, the key for this entry will include the name |
| + // of the file and the pref config, matching the pattern of gm_json.py's |
| + // IMAGE_FILENAME_PATTERN: "name_config.png" |
| + const SkString name_config = create_json_key(filename); |
| + |
| + // Record the new expectation. |
| + if (!FLAGS_createExpectationsPath.isEmpty()) { |
| + // Creates an Expectations object, and add it to the list to write. |
| + skiagm::Expectations expectation(bitmapAndDigest); |
| + Json::Value value = expectation.asJsonValue(); |
| + gExpectationsToWrite[name_config.c_str()] = value; |
| + } |
| + |
| + // Compare to the existing expectation. |
| + const skiagm::GmResultDigest& digest = bitmapAndDigest.fDigest; |
| if (!digest.isValid()) { |
| if (failureArray != NULL) { |
| failureArray->push_back().printf("decoded %s, but could not create a GmResultDigest.", |
| @@ -240,7 +300,7 @@ static bool compare_to_expectations_if_necessary(const skiagm::GmResultDigest& d |
| return false; |
| } |
| - skiagm::Expectations jsExpectation = gJsonExpectations->get(filename); |
| + skiagm::Expectations jsExpectation = gJsonExpectations->get(name_config.c_str()); |
| if (jsExpectation.empty()) { |
| if (missingArray != NULL) { |
| missingArray->push_back().printf("decoded %s, but could not find expectation.", |
| @@ -266,72 +326,82 @@ static bool compare_to_expectations_if_necessary(const skiagm::GmResultDigest& d |
| /** |
| * Helper function to write a bitmap subset to a file. Only called if subsets were created |
| - * and a writePath was provided. Creates a subdirectory called 'subsets' and writes a PNG to |
| - * that directory. Also creates a subdirectory called 'extracted' and writes a bitmap created |
| - * using extractSubset to a PNG in that directory. Both files will represent the same |
| - * subrectangle and have the same name for comparison. |
| + * and a writePath was provided. Behaves differently depending on |
| + * FLAGS_writeChecksumBasedFilenames. If true: |
|
epoger
2013/10/17 15:54:13
Again, rather than a static method depending on gl
|
| + * Writes the image to a PNG file named according to the digest hash, as described in |
| + * write_bitmap. |
| + * If false: |
| + * Creates a subdirectory called 'subsets' and writes a PNG to that directory. Also |
| + * creates a subdirectory called 'extracted' and writes a bitmap created using |
| + * extractSubset to a PNG in that directory. Both files will represent the same |
| + * subrectangle and have the same name for convenient comparison. In this case, the |
| + * digest is ignored. |
| * @param writePath Parent directory to hold the folders for the PNG files to write. Must |
|
epoger
2013/10/17 15:54:13
newline before first param (just to make it easier
scroggo
2013/10/18 21:28:03
Done.
|
| * not be NULL. |
| - * @param filename Basename of the original file. Used to name the new files. Must not be |
| - * NULL. |
| - * @param subsetDim String representing the dimensions of the subset. Used to name the new |
| - * files. Must not be NULL. |
| - * @param bitmapFromDecodeSubset Pointer to SkBitmap created by SkImageDecoder::DecodeSubset, |
| - * using rect as the area to decode. |
| + * @param subsetName Basename of the original file, with the dimensions of the subset tacked |
| + * on. Used to name the new file/folder. |
| + * @param bitmapAndDigestFromDecodeSubset SkBitmap (with digest) created by |
| + * SkImageDecoder::DecodeSubset, using rect as the area to decode. |
| * @param rect Rectangle of the area decoded into bitmapFromDecodeSubset. Used to call |
| * extractSubset on originalBitmap to create a bitmap with the same dimensions/pixels as |
| * bitmapFromDecodeSubset (assuming decodeSubset worked properly). |
| * @param originalBitmap SkBitmap decoded from the same stream as bitmapFromDecodeSubset, |
| * using SkImageDecoder::decode to get the entire image. Used to create a PNG file for |
| - * comparison to the PNG created by bitmapFromDecodeSubset. |
| + * comparison to the PNG created by bitmapAndDigestFromDecodeSubset's bitmap. |
| * @return bool Whether the function succeeded at drawing the decoded subset and the extracted |
| * subset to files. |
| */ |
| -static bool write_subset(const char* writePath, const char* filename, const char* subsetDim, |
| - SkBitmap* bitmapFromDecodeSubset, SkIRect rect, |
| - const SkBitmap& originalBitmap) { |
| +static bool write_subset(const char* writePath, const SkString& subsetName, |
| + const skiagm::BitmapAndDigest bitmapAndDigestFromDecodeSubset, |
| + SkIRect rect, const SkBitmap& originalBitmap) { |
| // All parameters must be valid. |
| SkASSERT(writePath != NULL); |
| - SkASSERT(filename != NULL); |
| - SkASSERT(subsetDim != NULL); |
| - SkASSERT(bitmapFromDecodeSubset != NULL); |
| - |
| - // Create a subdirectory to hold the results of decodeSubset. |
| - SkString dir = SkOSPath::SkPathJoin(writePath, "subsets"); |
| - if (!sk_mkdir(dir.c_str())) { |
| - gFailedSubsetDecodes.push_back().printf("Successfully decoded %s from %s, but failed to " |
| - "create a directory to write to.", subsetDim, |
| - filename); |
| - return false; |
| - } |
| - // Write the subset to a file whose name includes the dimensions. |
| - SkString suffix = SkStringPrintf("_%s.png", subsetDim); |
| - SkString outPath; |
| - make_outname(&outPath, dir.c_str(), filename, suffix.c_str()); |
| - SkAssertResult(write_bitmap(outPath.c_str(), *bitmapFromDecodeSubset)); |
| - gSuccessfulSubsetDecodes.push_back().printf("\twrote %s", outPath.c_str()); |
| - |
| - // Also use extractSubset from the original for visual comparison. |
| - // Write the result to a file in a separate subdirectory. |
| - SkBitmap extractedSubset; |
| - if (!originalBitmap.extractSubset(&extractedSubset, rect)) { |
| - gFailedSubsetDecodes.push_back().printf("Successfully decoded %s from %s, but failed to " |
| - "extract a similar subset for comparison.", |
| - subsetDim, filename); |
| - return false; |
| + SkString subsetPath; |
| + if (FLAGS_writeChecksumBasedFilenames) { |
| + subsetPath.set(writePath); |
| + } else { |
| + // Create a subdirectory to hold the results of decodeSubset. |
| + subsetPath = SkOSPath::SkPathJoin(writePath, "subsets"); |
| + if (!sk_mkdir(subsetPath.c_str())) { |
| + gFailedSubsetDecodes.push_back().printf("Successfully decoded subset %s, but " |
| + "failed to create a directory to write to.", |
| + subsetName.c_str()); |
| + return false; |
| + } |
| } |
| + SkAssertResult(write_bitmap(subsetPath.c_str(), subsetName.c_str(), |
| + bitmapAndDigestFromDecodeSubset)); |
| + gSuccessfulSubsetDecodes.push_back().printf("\twrote %s", subsetName.c_str()); |
| + |
| + if (!FLAGS_writeChecksumBasedFilenames) { |
| + // FIXME: The goal of extracting the subset is for visual comparison/using skdiff/skpdiff. |
| + // Currently disabling for writeChecksumBasedFilenames since it will be trickier to |
| + // determine which files to compare. |
| + |
| + // Also use extractSubset from the original for visual comparison. |
| + // Write the result to a file in a separate subdirectory. |
| + SkBitmap extractedSubset; |
| + if (!originalBitmap.extractSubset(&extractedSubset, rect)) { |
| + gFailedSubsetDecodes.push_back().printf("Successfully decoded subset %s, but failed " |
| + "to extract a similar subset for comparison.", |
| + subsetName.c_str()); |
| + return false; |
| + } |
| - SkString dirExtracted = SkOSPath::SkPathJoin(writePath, "extracted"); |
| - if (!sk_mkdir(dirExtracted.c_str())) { |
| - gFailedSubsetDecodes.push_back().printf("Successfully decoded %s from %s, but failed to " |
| - "create a directory for extractSubset comparison.", |
| - subsetDim, filename); |
| - return false; |
| - } |
| + SkString dirExtracted = SkOSPath::SkPathJoin(writePath, "extracted"); |
| + if (!sk_mkdir(dirExtracted.c_str())) { |
| + gFailedSubsetDecodes.push_back().printf("Successfully decoded subset%s, but failed " |
| + "to create a directory for extractSubset " |
| + "comparison.", |
| + subsetName.c_str()); |
| + return false; |
| + } |
| - make_outname(&outPath, dirExtracted.c_str(), filename, suffix.c_str()); |
| - SkAssertResult(write_bitmap(outPath.c_str(), extractedSubset)); |
| + skiagm::BitmapAndDigest bitmapAndDigestFromExtractSubset(extractedSubset); |
| + SkAssertResult(write_bitmap(dirExtracted.c_str(), subsetName.c_str(), |
| + bitmapAndDigestFromExtractSubset)); |
| + } |
| return true; |
| } |
| @@ -397,6 +467,20 @@ static void test_stream_without_length(const char srcPath[], SkImageDecoder* cod |
| } |
| #endif // defined(SK_BUILD_FOR_ANDROID) || defined(SK_BUILD_FOR_UNIX) |
| +/** |
| + * Replace all instances of oldChar with newChar in str. |
| + */ |
| +static void replace_char(SkString* str, const char oldChar, const char newChar) { |
|
epoger
2013/10/17 15:54:13
I don't think this needs to happen within this CL,
scroggo
2013/10/18 21:28:03
Done.
|
| + if (NULL == str) { |
| + return; |
| + } |
| + for (size_t i = 0; i < str->size(); ++i) { |
| + if (oldChar == str->operator[](i)) { |
| + str->operator[](i) = newChar; |
| + } |
| + } |
| +} |
| + |
| static void decodeFileAndWrite(const char srcPath[], const SkString* writePath) { |
| SkBitmap bitmap; |
| SkFILEStream stream(srcPath); |
| @@ -419,12 +503,15 @@ static void decodeFileAndWrite(const char srcPath[], const SkString* writePath) |
| // Create a string representing just the filename itself, for use in json expectations. |
| SkString basename = SkOSPath::SkBasename(srcPath); |
| + // Replace '_' with '-', so that the names can fit gm_json.py's IMAGE_FILENAME_PATTERN |
| + replace_char(&basename, '_', '-'); |
| const char* filename = basename.c_str(); |
| if (!codec->decode(&stream, &bitmap, gPrefConfig, |
| SkImageDecoder::kDecodePixels_Mode)) { |
| if (NULL != gJsonExpectations.get()) { |
| - skiagm::Expectations jsExpectations = gJsonExpectations->get(filename); |
| + const SkString name_config = create_json_key(filename); |
| + skiagm::Expectations jsExpectations = gJsonExpectations->get(name_config.c_str()); |
| if (jsExpectations.ignoreFailure()) { |
| // This is a known failure. |
| gKnownFailures.push_back().appendf( |
| @@ -462,41 +549,33 @@ static void decodeFileAndWrite(const char srcPath[], const SkString* writePath) |
| } |
| } |
| - skiagm::GmResultDigest digest(bitmap); |
| - if (compare_to_expectations_if_necessary(digest, filename, |
| - &gDecodeFailures, |
| - &gMissingExpectations, |
| - &gKnownFailures)) { |
| + skiagm::BitmapAndDigest bitmapAndDigest(bitmap); |
| + if (write_and_compare_expectations(bitmapAndDigest, filename, &gDecodeFailures, |
| + &gMissingExpectations, &gKnownFailures)) { |
| gSuccessfulDecodes.push_back().printf("%s [%d %d]", srcPath, bitmap.width(), |
| bitmap.height()); |
| } else if (!FLAGS_mismatchPath.isEmpty()) { |
| - SkString outPath; |
| - make_outname(&outPath, FLAGS_mismatchPath[0], srcPath, ".png"); |
| - if (write_bitmap(outPath.c_str(), bitmap)) { |
| - gSuccessfulDecodes.push_back().appendf("\twrote %s", outPath.c_str()); |
| + if (write_bitmap(FLAGS_mismatchPath[0], filename, bitmapAndDigest)) { |
| + gSuccessfulDecodes.push_back().appendf("\twrote %s", filename); |
| } else { |
| - gEncodeFailures.push_back().set(outPath); |
| + gEncodeFailures.push_back().set(filename); |
| } |
| } |
| // FIXME: This test could be run on windows/mac once we remove their dependence on |
| // getLength. See https://code.google.com/p/skia/issues/detail?id=1570 |
| #if defined(SK_BUILD_FOR_ANDROID) || defined(SK_BUILD_FOR_UNIX) |
| - test_stream_without_length(srcPath, codec, digest); |
| + test_stream_without_length(srcPath, codec, bitmapAndDigest.fDigest); |
| #endif |
| if (writePath != NULL) { |
| - SkString outPath; |
| - make_outname(&outPath, writePath->c_str(), srcPath, ".png"); |
| - if (write_bitmap(outPath.c_str(), bitmap)) { |
| - gSuccessfulDecodes.push_back().appendf("\twrote %s", outPath.c_str()); |
| + if (write_bitmap(writePath->c_str(), filename, bitmapAndDigest)) { |
| + gSuccessfulDecodes.push_back().appendf("\twrote %s", filename); |
| } else { |
| - gEncodeFailures.push_back().set(outPath); |
| + gEncodeFailures.push_back().set(filename); |
| } |
| } |
| - write_expectations(bitmap, filename); |
| - |
| if (FLAGS_testSubsetDecoding) { |
| SkDEBUGCODE(bool couldRewind =) stream.rewind(); |
| SkASSERT(couldRewind); |
| @@ -514,24 +593,23 @@ static void decodeFileAndWrite(const char srcPath[], const SkString* writePath) |
| SkString subsetDim = SkStringPrintf("[%d,%d,%d,%d]", rect.fLeft, rect.fTop, |
| rect.fRight, rect.fBottom); |
| if (codec->decodeSubset(&bitmapFromDecodeSubset, rect, gPrefConfig)) { |
| - SkString subsetName = SkStringPrintf("%s_%s", filename, subsetDim.c_str()); |
| - skiagm::GmResultDigest subsetDigest(bitmapFromDecodeSubset); |
| - if (compare_to_expectations_if_necessary(subsetDigest, |
| - subsetName.c_str(), |
| - &gFailedSubsetDecodes, |
| - &gMissingSubsetExpectations, |
| - &gKnownSubsetFailures)) { |
| + SkString subsetName = SkStringPrintf("%s-%s", filename, subsetDim.c_str()); |
| + skiagm::BitmapAndDigest subsetBitmapAndDigest(bitmapFromDecodeSubset); |
| + if (write_and_compare_expectations(subsetBitmapAndDigest, |
| + subsetName.c_str(), |
| + &gFailedSubsetDecodes, |
| + &gMissingSubsetExpectations, |
| + &gKnownSubsetFailures)) { |
| gSuccessfulSubsetDecodes.push_back().printf("Decoded subset %s from %s", |
| subsetDim.c_str(), srcPath); |
| } else if (!FLAGS_mismatchPath.isEmpty()) { |
| - write_subset(FLAGS_mismatchPath[0], filename, subsetDim.c_str(), |
| - &bitmapFromDecodeSubset, rect, bitmap); |
| + write_subset(FLAGS_mismatchPath[0], subsetName, |
| + subsetBitmapAndDigest, rect, bitmap); |
| } |
| - write_expectations(bitmapFromDecodeSubset, subsetName.c_str()); |
| if (writePath != NULL) { |
| - write_subset(writePath->c_str(), filename, subsetDim.c_str(), |
| - &bitmapFromDecodeSubset, rect, bitmap); |
| + write_subset(writePath->c_str(), subsetName, |
| + subsetBitmapAndDigest, rect, bitmap); |
| } |
| } else { |
| gFailedSubsetDecodes.push_back().printf("Failed to decode region %s from %s", |