| Index: tools/skimage_main.cpp
|
| diff --git a/tools/skimage_main.cpp b/tools/skimage_main.cpp
|
| index 5b8f09548535069b5f8c347cb12c1b24992bbd82..f5a2b3d738a6dce5709a8bbd8525849e5e364fcd 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 {
|
| @@ -80,15 +82,7 @@ static void make_outname(SkString* dst, const char outDir[], const char src[],
|
| const char suffix[]) {
|
| SkString basename = SkOSPath::SkBasename(src);
|
| dst->set(SkOSPath::SkPathJoin(outDir, basename.c_str()));
|
| - if (!dst->endsWith(suffix)) {
|
| - const char* cstyleDst = dst->c_str();
|
| - const char* dot = strrchr(cstyleDst, '.');
|
| - if (dot != NULL) {
|
| - int32_t index = SkToS32(dot - cstyleDst);
|
| - dst->remove(index, dst->size() - index);
|
| - }
|
| - dst->append(suffix);
|
| - }
|
| + dst->append(suffix);
|
| }
|
|
|
| // Store the names of the filenames to report later which ones failed, succeeded, and were
|
| @@ -129,8 +123,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
|
| + * 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 +175,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 +219,47 @@ static SkIRect generate_random_rect(SkRandom* rand, int32_t maxX, int32_t maxY)
|
| return rect;
|
| }
|
|
|
| +/**
|
| + * Return a string which includes the name of the file and the preferred config,
|
| + * as specified by "--config". The resulting string will match the pattern of
|
| + * gm_json.py's IMAGE_FILENAME_PATTERN: "filename_config.png"
|
| + */
|
| +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;
|
|
|
| /**
|
| - * If expectations are to be recorded, record the bitmap expectations into global
|
| + * If expectations are to be recorded, record the bitmap expectations into the global
|
| * expectations array.
|
| + * As is the case with reading expectations, the key used will combine the filename
|
| + * parameter with the preferred config, as specified by "--config", matching the
|
| + * pattern of gm_json.py's IMAGE_FILENAME_PATTERN: "filename_config.png"
|
| */
|
| -static void write_expectations(const SkBitmap& bitmap, const char* filename) {
|
| +static void write_expectations(const skiagm::BitmapAndDigest& bitmapAndDigest,
|
| + const char* filename) {
|
| + const SkString name_config = create_json_key(filename);
|
| if (!FLAGS_createExpectationsPath.isEmpty()) {
|
| // Creates an Expectations object, and add it to the list to write.
|
| - skiagm::Expectations expectation(bitmap);
|
| + skiagm::Expectations expectation(bitmapAndDigest);
|
| Json::Value value = expectation.asJsonValue();
|
| - gExpectationsToWrite[filename] = value;
|
| + gExpectationsToWrite[name_config.c_str()] = value;
|
| }
|
| }
|
|
|
| /**
|
| - * 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.
|
| + * If --readExpectationsPath is set, compare this bitmap to the json expectations
|
| + * provided.
|
| + *
|
| + * @param digest GmResultDigest, computed from the decoded bitmap, to compare to
|
| + * the existing expectation.
|
| + * @param filename String used to find the expected value. Will be combined with the
|
| + * preferred config, as specified by "--config", to match the pattern of
|
| + * gm_json.py's IMAGE_FILENAME_PATTERN: "filename_config.png". The resulting
|
| + * key will be used to find the proper expectations.
|
| * @param failureArray Array to add a failure message to on failure.
|
| * @param missingArray Array to add failure message to when missing image
|
| * expectation.
|
| @@ -228,6 +278,11 @@ static bool compare_to_expectations_if_necessary(const skiagm::GmResultDigest& d
|
| 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);
|
| +
|
| if (!digest.isValid()) {
|
| if (failureArray != NULL) {
|
| failureArray->push_back().printf("decoded %s, but could not create a GmResultDigest.",
|
| @@ -240,7 +295,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 +321,83 @@ 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:
|
| + * 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
|
| * 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 +463,21 @@ 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.
|
| + * TODO: Add this function to SkString and write tests for it.
|
| + */
|
| +static void replace_char(SkString* str, const char oldChar, const char newChar) {
|
| + 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 +500,18 @@ 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, '_', '-');
|
| + // Replace '.' with '-', so the output filename can still retain the original file extension,
|
| + // but still end up with only one '.', which denotes the actual extension of the final file.
|
| + 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,40 +549,34 @@ 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 (compare_to_expectations_if_necessary(bitmapAndDigest.fDigest, 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);
|
| + write_expectations(bitmapAndDigest, filename);
|
|
|
| if (FLAGS_testSubsetDecoding) {
|
| SkDEBUGCODE(bool couldRewind =) stream.rewind();
|
| @@ -514,9 +595,9 @@ 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,
|
| + SkString subsetName = SkStringPrintf("%s-%s", filename, subsetDim.c_str());
|
| + skiagm::BitmapAndDigest subsetBitmapAndDigest(bitmapFromDecodeSubset);
|
| + if (compare_to_expectations_if_necessary(subsetBitmapAndDigest.fDigest,
|
| subsetName.c_str(),
|
| &gFailedSubsetDecodes,
|
| &gMissingSubsetExpectations,
|
| @@ -524,14 +605,15 @@ static void decodeFileAndWrite(const char srcPath[], const SkString* writePath)
|
| 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());
|
| + write_expectations(subsetBitmapAndDigest, 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",
|
| @@ -587,7 +669,7 @@ static void decodeFileAndWrite(const char srcPath[], const SkString* writePath)
|
| if (writePath != NULL && type != SkImageEncoder::kPNG_Type) {
|
| // Write the encoded data to a file. Do not write to PNG, which was already written.
|
| SkString outPath;
|
| - make_outname(&outPath, writePath->c_str(), srcPath, suffix_for_type(type));
|
| + make_outname(&outPath, writePath->c_str(), filename, suffix_for_type(type));
|
| SkFILEWStream file(outPath.c_str());
|
| if(file.write(data->data(), data->size())) {
|
| gSuccessfulDecodes.push_back().appendf("\twrote %s", outPath.c_str());
|
|
|