/** * This function parses the given filename and stores the results in the given * families array. */ static void parseConfigFile(const char *filename, SkTDArray<FontFamily*> &families) { FILE* file = NULL; #if !defined(SK_BUILD_FOR_ANDROID_FRAMEWORK) // if we are using a version of Android prior to Android 4.2 (JellyBean MR1 // at API Level 17) then we need to look for files with a different suffix. char sdkVersion[PROP_VALUE_MAX]; __system_property_get("ro.build.version.sdk", sdkVersion); const int sdkVersionInt = atoi(sdkVersion); if (0 != *sdkVersion && sdkVersionInt < 17) { SkString basename; SkString updatedFilename; SkString locale = SkFontConfigParser::GetLocale(); basename.set(filename); // Remove the .xml suffix. We'll add it back in a moment. if (basename.endsWith(".xml")) { basename.resize(basename.size()-4); } // Try first with language and region updatedFilename.printf("%s-%s.xml", basename.c_str(), locale.c_str()); file = fopen(updatedFilename.c_str(), "r"); if (!file) { // If not found, try next with just language updatedFilename.printf("%s-%.2s.xml", basename.c_str(), locale.c_str()); file = fopen(updatedFilename.c_str(), "r"); } } #endif if (NULL == file) { file = fopen(filename, "r"); } // Some of the files we attempt to parse (in particular, /vendor/etc/fallback_fonts.xml) // are optional - failure here is okay because one of these optional files may not exist. if (NULL == file) { return; } XML_Parser parser = XML_ParserCreate(NULL); FamilyData *familyData = new FamilyData(&parser, families); XML_SetUserData(parser, familyData); XML_SetElementHandler(parser, startElementHandler, endElementHandler); char buffer[512]; bool done = false; while (!done) { fgets(buffer, sizeof(buffer), file); int len = strlen(buffer); if (feof(file) != 0) { done = true; } XML_Parse(parser, buffer, len, done); } XML_ParserFree(parser); fclose(file); }
/** * Use the current system locale (language and region) to open the best matching * customization. For example, when the language is Japanese, the sequence might be: * /system/etc/fallback_fonts-ja-JP.xml * /system/etc/fallback_fonts-ja.xml * /system/etc/fallback_fonts.xml */ FILE* openLocalizedFile(const char* origname) { FILE* file = 0; #if !defined(SK_BUILD_FOR_ANDROID_NDK) SkString basename; SkString filename; char language[3] = ""; char region[3] = ""; basename.set(origname); // Remove the .xml suffix. We'll add it back in a moment. if (basename.endsWith(".xml")) { basename.resize(basename.size()-4); } getLocale(language, region); // Try first with language and region filename.printf("%s-%s-%s.xml", basename.c_str(), language, region); file = fopen(filename.c_str(), "r"); if (!file) { // If not found, try next with just language filename.printf("%s-%s.xml", basename.c_str(), language); file = fopen(filename.c_str(), "r"); } #endif if (!file) { // If still not found, try just the original name file = fopen(origname, "r"); } return file; }
/** * Use the current system locale (language and region) to open the best matching * customization. For example, when the language is Japanese, the sequence might be: * /system/etc/fallback_fonts-ja-JP.xml * /system/etc/fallback_fonts-ja.xml * /system/etc/fallback_fonts.xml */ FILE* openLocalizedFile(const char* origname) { FILE* file = 0; SkString basename; SkString filename; AndroidLocale locale; basename.set(origname); // Remove the .xml suffix. We'll add it back in a moment. if (basename.endsWith(".xml")) { basename.resize(basename.size()-4); } getLocale(locale); // Try first with language and region filename.printf("%s-%s-%s.xml", basename.c_str(), locale.language, locale.region); file = fopen(filename.c_str(), "r"); if (!file) { // If not found, try next with just language filename.printf("%s-%s.xml", basename.c_str(), locale.language); file = fopen(filename.c_str(), "r"); if (!file) { // If still not found, try just the original name file = fopen(origname, "r"); } } return file; }
/** * Test SkOSPath::Join, SkOSPath::Basename, and SkOSPath::Dirname. * Will use SkOSPath::Join to append filename to dir, test that it works correctly, * and tests using SkOSPath::Basename on the result. * @param reporter Reporter for test conditions. * @param dir String representing the path to a folder. May or may not * end with SkOSPath::SEPARATOR. * @param filename String representing the basename of a file. Must NOT * contain SkOSPath::SEPARATOR. */ static void test_dir_with_file(skiatest::Reporter* reporter, SkString dir, SkString filename) { // If filename contains SkOSPath::SEPARATOR, the tests will fail. SkASSERT(!filename.contains(SkOSPath::SEPARATOR)); // Tests for SkOSPath::Join and SkOSPath::Basename // fullName should be "dir<SkOSPath::SEPARATOR>file" SkString fullName = SkOSPath::Join(dir.c_str(), filename.c_str()); // fullName should be the combined size of dir and file, plus one if // dir did not include the final path separator. size_t expectedSize = dir.size() + filename.size(); if (!dir.endsWith(SkOSPath::SEPARATOR) && !dir.isEmpty()) { expectedSize++; } REPORTER_ASSERT(reporter, fullName.size() == expectedSize); SkString basename = SkOSPath::Basename(fullName.c_str()); SkString dirname = SkOSPath::Dirname(fullName.c_str()); // basename should be the same as filename REPORTER_ASSERT(reporter, basename.equals(filename)); // dirname should be the same as dir with any trailing seperators removed. // Except when the the string is just "/". SkString strippedDir = dir; while (strippedDir.size() > 2 && strippedDir[strippedDir.size() - 1] == SkOSPath::SEPARATOR) { strippedDir.remove(strippedDir.size() - 1, 1); } if (!dirname.equals(strippedDir)) { SkDebugf("OOUCH %s %s %s\n", dir.c_str(), strippedDir.c_str(), dirname.c_str()); } REPORTER_ASSERT(reporter, dirname.equals(strippedDir)); // basename will not contain a path separator REPORTER_ASSERT(reporter, !basename.contains(SkOSPath::SEPARATOR)); // Now take the basename of filename, which should be the same as filename. basename = SkOSPath::Basename(filename.c_str()); REPORTER_ASSERT(reporter, basename.equals(filename)); }
int tool_main(int argc, char** argv) { DiffMetricProc diffProc = compute_diff_pmcolor; int (*sortProc)(const void*, const void*) = compare<CompareDiffMetrics>; // Maximum error tolerated in any one color channel in any one pixel before // a difference is reported. int colorThreshold = 0; SkString baseDir; SkString comparisonDir; SkString outputDir; StringArray matchSubstrings; StringArray nomatchSubstrings; bool generateDiffs = true; bool listFilenames = false; bool printDirNames = true; bool recurseIntoSubdirs = true; bool verbose = false; bool listFailingBase = false; RecordArray differences; DiffSummary summary; bool failOnResultType[DiffRecord::kResultCount]; for (int i = 0; i < DiffRecord::kResultCount; i++) { failOnResultType[i] = false; } bool failOnStatusType[DiffResource::kStatusCount][DiffResource::kStatusCount]; for (int base = 0; base < DiffResource::kStatusCount; ++base) { for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) { failOnStatusType[base][comparison] = false; } } int i; int numUnflaggedArguments = 0; for (i = 1; i < argc; i++) { if (!strcmp(argv[i], "--failonresult")) { if (argc == ++i) { SkDebugf("failonresult expects one argument.\n"); continue; } DiffRecord::Result type = DiffRecord::getResultByName(argv[i]); if (type != DiffRecord::kResultCount) { failOnResultType[type] = true; } else { SkDebugf("ignoring unrecognized result <%s>\n", argv[i]); } continue; } if (!strcmp(argv[i], "--failonstatus")) { if (argc == ++i) { SkDebugf("failonstatus missing base status.\n"); continue; } bool baseStatuses[DiffResource::kStatusCount]; if (!DiffResource::getMatchingStatuses(argv[i], baseStatuses)) { SkDebugf("unrecognized base status <%s>\n", argv[i]); } if (argc == ++i) { SkDebugf("failonstatus missing comparison status.\n"); continue; } bool comparisonStatuses[DiffResource::kStatusCount]; if (!DiffResource::getMatchingStatuses(argv[i], comparisonStatuses)) { SkDebugf("unrecognized comarison status <%s>\n", argv[i]); } for (int base = 0; base < DiffResource::kStatusCount; ++base) { for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) { failOnStatusType[base][comparison] |= baseStatuses[base] && comparisonStatuses[comparison]; } } continue; } if (!strcmp(argv[i], "--help")) { usage(argv[0]); return kNoError; } if (!strcmp(argv[i], "--listfilenames")) { listFilenames = true; continue; } if (!strcmp(argv[i], "--verbose")) { verbose = true; continue; } if (!strcmp(argv[i], "--match")) { matchSubstrings.push(new SkString(argv[++i])); continue; } if (!strcmp(argv[i], "--nodiffs")) { generateDiffs = false; continue; } if (!strcmp(argv[i], "--nomatch")) { nomatchSubstrings.push(new SkString(argv[++i])); continue; } if (!strcmp(argv[i], "--noprintdirs")) { printDirNames = false; continue; } if (!strcmp(argv[i], "--norecurse")) { recurseIntoSubdirs = false; continue; } if (!strcmp(argv[i], "--sortbymaxmismatch")) { sortProc = compare<CompareDiffMaxMismatches>; continue; } if (!strcmp(argv[i], "--sortbymismatch")) { sortProc = compare<CompareDiffMeanMismatches>; continue; } if (!strcmp(argv[i], "--threshold")) { colorThreshold = atoi(argv[++i]); continue; } if (!strcmp(argv[i], "--weighted")) { sortProc = compare<CompareDiffWeighted>; continue; } if (argv[i][0] != '-') { switch (numUnflaggedArguments++) { case 0: baseDir.set(argv[i]); continue; case 1: comparisonDir.set(argv[i]); continue; case 2: outputDir.set(argv[i]); continue; default: SkDebugf("extra unflagged argument <%s>\n", argv[i]); usage(argv[0]); return kGenericError; } } if (!strcmp(argv[i], "--listFailingBase")) { listFailingBase = true; continue; } SkDebugf("Unrecognized argument <%s>\n", argv[i]); usage(argv[0]); return kGenericError; } if (numUnflaggedArguments == 2) { outputDir = comparisonDir; } else if (numUnflaggedArguments != 3) { usage(argv[0]); return kGenericError; } if (!baseDir.endsWith(PATH_DIV_STR)) { baseDir.append(PATH_DIV_STR); } if (printDirNames) { printf("baseDir is [%s]\n", baseDir.c_str()); } if (!comparisonDir.endsWith(PATH_DIV_STR)) { comparisonDir.append(PATH_DIV_STR); } if (printDirNames) { printf("comparisonDir is [%s]\n", comparisonDir.c_str()); } if (!outputDir.endsWith(PATH_DIV_STR)) { outputDir.append(PATH_DIV_STR); } if (generateDiffs) { if (printDirNames) { printf("writing diffs to outputDir is [%s]\n", outputDir.c_str()); } } else { if (printDirNames) { printf("not writing any diffs to outputDir [%s]\n", outputDir.c_str()); } outputDir.set(""); } // If no matchSubstrings were specified, match ALL strings // (except for whatever nomatchSubstrings were specified, if any). if (matchSubstrings.isEmpty()) { matchSubstrings.push(new SkString("")); } create_diff_images(diffProc, colorThreshold, &differences, baseDir, comparisonDir, outputDir, matchSubstrings, nomatchSubstrings, recurseIntoSubdirs, generateDiffs, verbose, &summary); summary.print(listFilenames, failOnResultType, failOnStatusType); if (listFailingBase) { summary.printfFailingBaseNames("\n"); } if (differences.count()) { qsort(differences.begin(), differences.count(), sizeof(DiffRecord*), sortProc); } if (generateDiffs) { print_diff_page(summary.fNumMatches, colorThreshold, differences, baseDir, comparisonDir, outputDir); } for (i = 0; i < differences.count(); i++) { delete differences[i]; } matchSubstrings.deleteAll(); nomatchSubstrings.deleteAll(); int num_failing_results = 0; for (int i = 0; i < DiffRecord::kResultCount; i++) { if (failOnResultType[i]) { num_failing_results += summary.fResultsOfType[i].count(); } } if (!failOnResultType[DiffRecord::kCouldNotCompare_Result]) { for (int base = 0; base < DiffResource::kStatusCount; ++base) { for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) { if (failOnStatusType[base][comparison]) { num_failing_results += summary.fStatusOfType[base][comparison].count(); } } } } // On Linux (and maybe other platforms too), any results outside of the // range [0...255] are wrapped (mod 256). Do the conversion ourselves, to // make sure that we only return 0 when there were no failures. return (num_failing_results > 255) ? 255 : num_failing_results; }
/** * Given either a SkStream or a SkData, try to decode the encoded * image using the specified options and report errors. */ static void test_options(skiatest::Reporter* reporter, const SkDecodingImageGenerator::Options& opts, SkStreamRewindable* encodedStream, SkData* encodedData, bool useData, const SkString& path) { SkBitmap bm; bool success = false; if (useData) { if (NULL == encodedData) { return; } success = SkInstallDiscardablePixelRef( SkDecodingImageGenerator::Create(encodedData, opts), &bm); } else { if (NULL == encodedStream) { return; } success = SkInstallDiscardablePixelRef( SkDecodingImageGenerator::Create(encodedStream->duplicate(), opts), &bm); } if (!success) { if (opts.fUseRequestedColorType && (kARGB_4444_SkColorType == opts.fRequestedColorType)) { return; // Ignore known conversion inabilities. } // If we get here, it's a failure and we will need more // information about why it failed. ERRORF(reporter, "Bounds decode failed [sampleSize=%d dither=%s " "colorType=%s %s]", opts.fSampleSize, yn(opts.fDitherImage), options_colorType(opts), path.c_str()); return; } #if defined(SK_BUILD_FOR_ANDROID) || defined(SK_BUILD_FOR_UNIX) // Android is the only system that use Skia's image decoders in // production. For now, we'll only verify that samplesize works // on systems where it already is known to work. REPORTER_ASSERT(reporter, check_rounding(bm.height(), kExpectedHeight, opts.fSampleSize)); REPORTER_ASSERT(reporter, check_rounding(bm.width(), kExpectedWidth, opts.fSampleSize)); // The ImageDecoder API doesn't guarantee that SampleSize does // anything at all, but the decoders that this test excercises all // produce an output size in the following range: // (((sample_size * out_size) > (in_size - sample_size)) // && out_size <= SkNextPow2(((in_size - 1) / sample_size) + 1)); #endif // SK_BUILD_FOR_ANDROID || SK_BUILD_FOR_UNIX SkAutoLockPixels alp(bm); if (bm.getPixels() == NULL) { ERRORF(reporter, "Pixel decode failed [sampleSize=%d dither=%s " "colorType=%s %s]", opts.fSampleSize, yn(opts.fDitherImage), options_colorType(opts), path.c_str()); return; } SkColorType requestedColorType = opts.fRequestedColorType; REPORTER_ASSERT(reporter, (!opts.fUseRequestedColorType) || (bm.colorType() == requestedColorType)); // Condition under which we should check the decoding results: if ((kN32_SkColorType == bm.colorType()) && (!path.endsWith(".jpg")) // lossy && (opts.fSampleSize == 1)) { // scaled const SkColor* correctPixels = kExpectedPixels; SkASSERT(bm.height() == kExpectedHeight); SkASSERT(bm.width() == kExpectedWidth); int pixelErrors = 0; for (int y = 0; y < bm.height(); ++y) { for (int x = 0; x < bm.width(); ++x) { if (*correctPixels != bm.getColor(x, y)) { ++pixelErrors; } ++correctPixels; } } if (pixelErrors != 0) { ERRORF(reporter, "Pixel-level mismatch (%d of %d) " "[sampleSize=%d dither=%s colorType=%s %s]", pixelErrors, kExpectedHeight * kExpectedWidth, opts.fSampleSize, yn(opts.fDitherImage), options_colorType(opts), path.c_str()); } } }
int tool_main(int argc, char** argv) { DiffMetricProc diffProc = compute_diff_pmcolor; // Maximum error tolerated in any one color channel in any one pixel before // a difference is reported. int colorThreshold = 0; SkString baseFile; SkString baseLabel; SkString comparisonFile; SkString comparisonLabel; SkString outputDir; bool listFilenames = false; bool failOnResultType[DiffRecord::kResultCount]; for (int i = 0; i < DiffRecord::kResultCount; i++) { failOnResultType[i] = false; } bool failOnStatusType[DiffResource::kStatusCount][DiffResource::kStatusCount]; for (int base = 0; base < DiffResource::kStatusCount; ++base) { for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) { failOnStatusType[base][comparison] = false; } } int i; int numUnflaggedArguments = 0; int numLabelArguments = 0; for (i = 1; i < argc; i++) { if (!strcmp(argv[i], "--failonresult")) { if (argc == ++i) { SkDebugf("failonresult expects one argument.\n"); continue; } DiffRecord::Result type = DiffRecord::getResultByName(argv[i]); if (type != DiffRecord::kResultCount) { failOnResultType[type] = true; } else { SkDebugf("ignoring unrecognized result <%s>\n", argv[i]); } continue; } if (!strcmp(argv[i], "--failonstatus")) { if (argc == ++i) { SkDebugf("failonstatus missing base status.\n"); continue; } bool baseStatuses[DiffResource::kStatusCount]; if (!DiffResource::getMatchingStatuses(argv[i], baseStatuses)) { SkDebugf("unrecognized base status <%s>\n", argv[i]); } if (argc == ++i) { SkDebugf("failonstatus missing comparison status.\n"); continue; } bool comparisonStatuses[DiffResource::kStatusCount]; if (!DiffResource::getMatchingStatuses(argv[i], comparisonStatuses)) { SkDebugf("unrecognized comarison status <%s>\n", argv[i]); } for (int base = 0; base < DiffResource::kStatusCount; ++base) { for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) { failOnStatusType[base][comparison] |= baseStatuses[base] && comparisonStatuses[comparison]; } } continue; } if (!strcmp(argv[i], "--help")) { usage(argv[0]); return kNoError; } if (!strcmp(argv[i], "--listfilenames")) { listFilenames = true; continue; } if (!strcmp(argv[i], "--outputdir")) { if (argc == ++i) { SkDebugf("outputdir expects one argument.\n"); continue; } outputDir.set(argv[i]); continue; } if (!strcmp(argv[i], "--threshold")) { colorThreshold = atoi(argv[++i]); continue; } if (!strcmp(argv[i], "-u")) { //we don't produce unified diffs, ignore parameter to work with svn diff continue; } if (!strcmp(argv[i], "-L")) { if (argc == ++i) { SkDebugf("label expects one argument.\n"); continue; } switch (numLabelArguments++) { case 0: baseLabel.set(argv[i]); continue; case 1: comparisonLabel.set(argv[i]); continue; default: SkDebugf("extra label argument <%s>\n", argv[i]); usage(argv[0]); return kGenericError; } continue; } if (argv[i][0] != '-') { switch (numUnflaggedArguments++) { case 0: baseFile.set(argv[i]); continue; case 1: comparisonFile.set(argv[i]); continue; default: SkDebugf("extra unflagged argument <%s>\n", argv[i]); usage(argv[0]); return kGenericError; } } SkDebugf("Unrecognized argument <%s>\n", argv[i]); usage(argv[0]); return kGenericError; } if (numUnflaggedArguments != 2) { usage(argv[0]); return kGenericError; } if (listFilenames) { printf("Base file is [%s]\n", baseFile.c_str()); } if (listFilenames) { printf("Comparison file is [%s]\n", comparisonFile.c_str()); } if (outputDir.isEmpty()) { if (listFilenames) { printf("Not writing any diffs. No output dir specified.\n"); } } else { if (!outputDir.endsWith(PATH_DIV_STR)) { outputDir.append(PATH_DIV_STR); } if (listFilenames) { printf("Writing diffs. Output dir is [%s]\n", outputDir.c_str()); } } // Some obscure documentation about diff/patch labels: // // Posix says the format is: <filename><tab><date> // It also states that if a filename contains <tab> or <newline> // the result is implementation defined // // Svn diff --diff-cmd provides labels of the form: <filename><tab><revision> // // Git diff --ext-diff does not supply arguments compatible with diff. // However, it does provide the filename directly. // skimagediff_git.sh: skimagediff %2 %5 -L "%1\t(%3)" -L "%1\t(%6)" // // Git difftool sets $LOCAL, $REMOTE, $MERGED, and $BASE instead of command line parameters. // difftool.<>.cmd: skimagediff $LOCAL $REMOTE -L "$MERGED\t(local)" -L "$MERGED\t(remote)" // // Diff will write any specified label verbatim. Without a specified label diff will write // <filename><tab><date> // However, diff will encode the filename as a cstring if the filename contains // Any of <space> or <double quote> // A char less than 32 // Any escapable character \\, \a, \b, \t, \n, \v, \f, \r // // Patch decodes: // If first <non-white-space> is <double quote>, parse filename from cstring. // If there is a <tab> after the first <non-white-space>, filename is // [first <non-white-space>, the next run of <white-space> with an embedded <tab>). // Otherwise the filename is [first <non-space>, the next <white-space>). // // The filename /dev/null means the file does not exist (used in adds and deletes). // Considering the above, skimagediff will consider the contents of a -L parameter as // <filename>(\t<specifier>)? SkString outputFile; if (baseLabel.isEmpty()) { baseLabel.set(baseFile); outputFile = baseLabel; } else { const char* baseLabelCstr = baseLabel.c_str(); const char* tab = strchr(baseLabelCstr, '\t'); if (nullptr == tab) { outputFile = baseLabel; } else { outputFile.set(baseLabelCstr, tab - baseLabelCstr); } } if (comparisonLabel.isEmpty()) { comparisonLabel.set(comparisonFile); } printf("Base: %s\n", baseLabel.c_str()); printf("Comparison: %s\n", comparisonLabel.c_str()); DiffRecord dr; create_diff_images(diffProc, colorThreshold, baseFile, comparisonFile, outputDir, outputFile, &dr); if (DiffResource::isStatusFailed(dr.fBase.fStatus)) { printf("Base %s.\n", DiffResource::getStatusDescription(dr.fBase.fStatus)); } if (DiffResource::isStatusFailed(dr.fComparison.fStatus)) { printf("Comparison %s.\n", DiffResource::getStatusDescription(dr.fComparison.fStatus)); } printf("Base and Comparison %s.\n", DiffRecord::getResultDescription(dr.fResult)); if (DiffRecord::kDifferentPixels_Result == dr.fResult) { printf("%.4f%% of pixels differ", 100 * dr.fFractionDifference); printf(" (%.4f%% weighted)", 100 * dr.fWeightedFraction); if (dr.fFractionDifference < 0.01) { printf(" %d pixels", static_cast<int>(dr.fFractionDifference * dr.fBase.fBitmap.width() * dr.fBase.fBitmap.height())); } printf("\nAverage color mismatch: "); printf("%d", static_cast<int>(MAX3(dr.fAverageMismatchR, dr.fAverageMismatchG, dr.fAverageMismatchB))); printf("\nMax color mismatch: "); printf("%d", MAX3(dr.fMaxMismatchR, dr.fMaxMismatchG, dr.fMaxMismatchB)); printf("\n"); } printf("\n"); int num_failing_results = 0; if (failOnResultType[dr.fResult]) { ++num_failing_results; } if (failOnStatusType[dr.fBase.fStatus][dr.fComparison.fStatus]) { ++num_failing_results; } return num_failing_results; }