2 * Copyright 2012 Google Inc.
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
8 #include "skdiff_utils.h"
11 #include "SkImageDecoder.h"
12 #include "SkImageEncoder.h"
14 #include "SkTDArray.h"
15 #include "SkTemplates.h"
20 /// If outputDir.isEmpty(), don't write out diff files.
21 static void create_diff_images (DiffMetricProc dmp,
22 const int colorThreshold,
23 const SkString& baseFile,
24 const SkString& comparisonFile,
25 const SkString& outputDir,
26 const SkString& outputFilename,
28 SkASSERT(!baseFile.isEmpty());
29 SkASSERT(!comparisonFile.isEmpty());
31 drp->fBase.fFilename = baseFile;
32 drp->fBase.fFullPath = baseFile;
33 drp->fBase.fStatus = DiffResource::kSpecified_Status;
35 drp->fComparison.fFilename = comparisonFile;
36 drp->fComparison.fFullPath = comparisonFile;
37 drp->fComparison.fStatus = DiffResource::kSpecified_Status;
39 SkAutoDataUnref baseFileBits(read_file(drp->fBase.fFullPath.c_str()));
41 drp->fBase.fStatus = DiffResource::kRead_Status;
43 SkAutoDataUnref comparisonFileBits(read_file(drp->fComparison.fFullPath.c_str()));
44 if (comparisonFileBits) {
45 drp->fComparison.fStatus = DiffResource::kRead_Status;
47 if (nullptr == baseFileBits || nullptr == comparisonFileBits) {
48 if (nullptr == baseFileBits) {
49 drp->fBase.fStatus = DiffResource::kCouldNotRead_Status;
51 if (nullptr == comparisonFileBits) {
52 drp->fComparison.fStatus = DiffResource::kCouldNotRead_Status;
54 drp->fResult = DiffRecord::kCouldNotCompare_Result;
58 if (are_buffers_equal(baseFileBits, comparisonFileBits)) {
59 drp->fResult = DiffRecord::kEqualBits_Result;
63 get_bitmap(baseFileBits, drp->fBase, SkImageDecoder::kDecodePixels_Mode);
64 get_bitmap(comparisonFileBits, drp->fComparison, SkImageDecoder::kDecodePixels_Mode);
65 if (DiffResource::kDecoded_Status != drp->fBase.fStatus ||
66 DiffResource::kDecoded_Status != drp->fComparison.fStatus)
68 drp->fResult = DiffRecord::kCouldNotCompare_Result;
72 create_and_write_diff_image(drp, dmp, colorThreshold, outputDir, outputFilename);
73 //TODO: copy fBase.fFilename and fComparison.fFilename to outputDir
74 // svn and git often present tmp files to diff tools which are promptly deleted
76 //TODO: serialize drp to outputDir
77 // write a tool to deserialize them and call print_diff_page
79 SkASSERT(DiffRecord::kUnknown_Result != drp->fResult);
82 static void usage (char * argv0) {
83 SkDebugf("Skia image diff tool\n");
86 " %s <baseFile> <comparisonFile>\n" , argv0);
89 "\n --failonresult <result>: After comparing all file pairs, exit with nonzero"
90 "\n return code (number of file pairs yielding this"
91 "\n result) if any file pairs yielded this result."
92 "\n This flag may be repeated, in which case the"
93 "\n return code will be the number of fail pairs"
94 "\n yielding ANY of these results."
95 "\n --failonstatus <baseStatus> <comparisonStatus>: exit with nonzero return"
96 "\n code if any file pairs yeilded this status."
97 "\n --help: display this info"
98 "\n --listfilenames: list all filenames for each result type in stdout"
99 "\n --nodiffs: don't write out image diffs, just generate report on stdout"
100 "\n --outputdir: directory to write difference images"
101 "\n --threshold <n>: only report differences > n (per color channel) [default 0]"
102 "\n -u: ignored. Recognized for compatibility with svn diff."
103 "\n -L: first occurrence label for base, second occurrence label for comparison."
104 "\n Labels must be of the form \"<filename>(\t<specifier>)?\"."
105 "\n The base <filename> will be used to create files in outputdir."
107 "\n baseFile: baseline image file."
108 "\n comparisonFile: comparison image file"
110 "\nIf no sort is specified, it will sort by fraction of pixels mismatching."
114 const int kNoError = 0;
115 const int kGenericError = -1;
117 int tool_main(int argc, char** argv);
118 int tool_main(int argc, char** argv) {
119 DiffMetricProc diffProc = compute_diff_pmcolor;
121 // Maximum error tolerated in any one color channel in any one pixel before
122 // a difference is reported.
123 int colorThreshold = 0;
126 SkString comparisonFile;
127 SkString comparisonLabel;
130 bool listFilenames = false;
132 bool failOnResultType[DiffRecord::kResultCount];
133 for (int i = 0; i < DiffRecord::kResultCount; i++) {
134 failOnResultType[i] = false;
137 bool failOnStatusType[DiffResource::kStatusCount][DiffResource::kStatusCount];
138 for (int base = 0; base < DiffResource::kStatusCount; ++base) {
139 for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) {
140 failOnStatusType[base][comparison] = false;
145 int numUnflaggedArguments = 0;
146 int numLabelArguments = 0;
147 for (i = 1; i < argc; i++) {
148 if (!strcmp(argv[i], "--failonresult")) {
150 SkDebugf("failonresult expects one argument.\n");
153 DiffRecord::Result type = DiffRecord::getResultByName(argv[i]);
154 if (type != DiffRecord::kResultCount) {
155 failOnResultType[type] = true;
157 SkDebugf("ignoring unrecognized result <%s>\n", argv[i]);
161 if (!strcmp(argv[i], "--failonstatus")) {
163 SkDebugf("failonstatus missing base status.\n");
166 bool baseStatuses[DiffResource::kStatusCount];
167 if (!DiffResource::getMatchingStatuses(argv[i], baseStatuses)) {
168 SkDebugf("unrecognized base status <%s>\n", argv[i]);
172 SkDebugf("failonstatus missing comparison status.\n");
175 bool comparisonStatuses[DiffResource::kStatusCount];
176 if (!DiffResource::getMatchingStatuses(argv[i], comparisonStatuses)) {
177 SkDebugf("unrecognized comarison status <%s>\n", argv[i]);
180 for (int base = 0; base < DiffResource::kStatusCount; ++base) {
181 for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) {
182 failOnStatusType[base][comparison] |=
183 baseStatuses[base] && comparisonStatuses[comparison];
188 if (!strcmp(argv[i], "--help")) {
192 if (!strcmp(argv[i], "--listfilenames")) {
193 listFilenames = true;
196 if (!strcmp(argv[i], "--outputdir")) {
198 SkDebugf("outputdir expects one argument.\n");
201 outputDir.set(argv[i]);
204 if (!strcmp(argv[i], "--threshold")) {
205 colorThreshold = atoi(argv[++i]);
208 if (!strcmp(argv[i], "-u")) {
209 //we don't produce unified diffs, ignore parameter to work with svn diff
212 if (!strcmp(argv[i], "-L")) {
214 SkDebugf("label expects one argument.\n");
217 switch (numLabelArguments++) {
219 baseLabel.set(argv[i]);
222 comparisonLabel.set(argv[i]);
225 SkDebugf("extra label argument <%s>\n", argv[i]);
227 return kGenericError;
231 if (argv[i][0] != '-') {
232 switch (numUnflaggedArguments++) {
234 baseFile.set(argv[i]);
237 comparisonFile.set(argv[i]);
240 SkDebugf("extra unflagged argument <%s>\n", argv[i]);
242 return kGenericError;
246 SkDebugf("Unrecognized argument <%s>\n", argv[i]);
248 return kGenericError;
251 if (numUnflaggedArguments != 2) {
253 return kGenericError;
257 printf("Base file is [%s]\n", baseFile.c_str());
261 printf("Comparison file is [%s]\n", comparisonFile.c_str());
264 if (outputDir.isEmpty()) {
266 printf("Not writing any diffs. No output dir specified.\n");
269 if (!outputDir.endsWith(PATH_DIV_STR)) {
270 outputDir.append(PATH_DIV_STR);
273 printf("Writing diffs. Output dir is [%s]\n", outputDir.c_str());
277 // Some obscure documentation about diff/patch labels:
279 // Posix says the format is: <filename><tab><date>
280 // It also states that if a filename contains <tab> or <newline>
281 // the result is implementation defined
283 // Svn diff --diff-cmd provides labels of the form: <filename><tab><revision>
285 // Git diff --ext-diff does not supply arguments compatible with diff.
286 // However, it does provide the filename directly.
287 // skimagediff_git.sh: skimagediff %2 %5 -L "%1\t(%3)" -L "%1\t(%6)"
289 // Git difftool sets $LOCAL, $REMOTE, $MERGED, and $BASE instead of command line parameters.
290 // difftool.<>.cmd: skimagediff $LOCAL $REMOTE -L "$MERGED\t(local)" -L "$MERGED\t(remote)"
292 // Diff will write any specified label verbatim. Without a specified label diff will write
293 // <filename><tab><date>
294 // However, diff will encode the filename as a cstring if the filename contains
295 // Any of <space> or <double quote>
296 // A char less than 32
297 // Any escapable character \\, \a, \b, \t, \n, \v, \f, \r
300 // If first <non-white-space> is <double quote>, parse filename from cstring.
301 // If there is a <tab> after the first <non-white-space>, filename is
302 // [first <non-white-space>, the next run of <white-space> with an embedded <tab>).
303 // Otherwise the filename is [first <non-space>, the next <white-space>).
305 // The filename /dev/null means the file does not exist (used in adds and deletes).
307 // Considering the above, skimagediff will consider the contents of a -L parameter as
308 // <filename>(\t<specifier>)?
311 if (baseLabel.isEmpty()) {
312 baseLabel.set(baseFile);
313 outputFile = baseLabel;
315 const char* baseLabelCstr = baseLabel.c_str();
316 const char* tab = strchr(baseLabelCstr, '\t');
317 if (nullptr == tab) {
318 outputFile = baseLabel;
320 outputFile.set(baseLabelCstr, tab - baseLabelCstr);
323 if (comparisonLabel.isEmpty()) {
324 comparisonLabel.set(comparisonFile);
326 printf("Base: %s\n", baseLabel.c_str());
327 printf("Comparison: %s\n", comparisonLabel.c_str());
330 create_diff_images(diffProc, colorThreshold, baseFile, comparisonFile, outputDir, outputFile,
333 if (DiffResource::isStatusFailed(dr.fBase.fStatus)) {
334 printf("Base %s.\n", DiffResource::getStatusDescription(dr.fBase.fStatus));
336 if (DiffResource::isStatusFailed(dr.fComparison.fStatus)) {
337 printf("Comparison %s.\n", DiffResource::getStatusDescription(dr.fComparison.fStatus));
339 printf("Base and Comparison %s.\n", DiffRecord::getResultDescription(dr.fResult));
341 if (DiffRecord::kDifferentPixels_Result == dr.fResult) {
342 printf("%.4f%% of pixels differ", 100 * dr.fFractionDifference);
343 printf(" (%.4f%% weighted)", 100 * dr.fWeightedFraction);
344 if (dr.fFractionDifference < 0.01) {
345 printf(" %d pixels", static_cast<int>(dr.fFractionDifference *
346 dr.fBase.fBitmap.width() *
347 dr.fBase.fBitmap.height()));
350 printf("\nAverage color mismatch: ");
351 printf("%d", static_cast<int>(MAX3(dr.fAverageMismatchR,
352 dr.fAverageMismatchG,
353 dr.fAverageMismatchB)));
354 printf("\nMax color mismatch: ");
355 printf("%d", MAX3(dr.fMaxMismatchR,
362 int num_failing_results = 0;
363 if (failOnResultType[dr.fResult]) {
364 ++num_failing_results;
366 if (failOnStatusType[dr.fBase.fStatus][dr.fComparison.fStatus]) {
367 ++num_failing_results;
370 return num_failing_results;
373 #if !defined SK_BUILD_FOR_IOS
374 int main(int argc, char * const argv[]) {
375 return tool_main(argc, (char**) argv);