1 /* 2 * Copyright 2012 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 #include "skdiff.h" 8 #include "skdiff_utils.h" 9 #include "SkBitmap.h" 10 #include "SkData.h" 11 #include "SkImageDecoder.h" 12 #include "SkImageEncoder.h" 13 #include "SkOSFile.h" 14 #include "SkTDArray.h" 15 #include "SkTemplates.h" 16 #include "SkTypes.h" 17 18 #include <stdio.h> 19 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, 27 DiffRecord* drp) { 28 SkASSERT(!baseFile.isEmpty()); 29 SkASSERT(!comparisonFile.isEmpty()); 30 31 drp->fBase.fFilename = baseFile; 32 drp->fBase.fFullPath = baseFile; 33 drp->fBase.fStatus = DiffResource::kSpecified_Status; 34 35 drp->fComparison.fFilename = comparisonFile; 36 drp->fComparison.fFullPath = comparisonFile; 37 drp->fComparison.fStatus = DiffResource::kSpecified_Status; 38 39 SkAutoDataUnref baseFileBits(read_file(drp->fBase.fFullPath.c_str())); 40 if (NULL != baseFileBits) { 41 drp->fBase.fStatus = DiffResource::kRead_Status; 42 } 43 SkAutoDataUnref comparisonFileBits(read_file(drp->fComparison.fFullPath.c_str())); 44 if (NULL != comparisonFileBits) { 45 drp->fComparison.fStatus = DiffResource::kRead_Status; 46 } 47 if (NULL == baseFileBits || NULL == comparisonFileBits) { 48 if (NULL == baseFileBits) { 49 drp->fBase.fStatus = DiffResource::kCouldNotRead_Status; 50 } 51 if (NULL == comparisonFileBits) { 52 drp->fComparison.fStatus = DiffResource::kCouldNotRead_Status; 53 } 54 drp->fResult = DiffRecord::kCouldNotCompare_Result; 55 return; 56 } 57 58 if (are_buffers_equal(baseFileBits, comparisonFileBits)) { 59 drp->fResult = DiffRecord::kEqualBits_Result; 60 return; 61 } 62 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) 67 { 68 drp->fResult = DiffRecord::kCouldNotCompare_Result; 69 return; 70 } 71 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 75 76 //TODO: serialize drp to outputDir 77 // write a tool to deserialize them and call print_diff_page 78 79 SkASSERT(DiffRecord::kUnknown_Result != drp->fResult); 80 } 81 82 static void usage (char * argv0) { 83 SkDebugf("Skia image diff tool\n"); 84 SkDebugf("\n" 85 "Usage: \n" 86 " %s <baseFile> <comparisonFile>\n" , argv0); 87 SkDebugf( 88 "\nArguments:" 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." 106 "\n" 107 "\n baseFile: baseline image file." 108 "\n comparisonFile: comparison image file" 109 "\n" 110 "\nIf no sort is specified, it will sort by fraction of pixels mismatching." 111 "\n"); 112 } 113 114 const int kNoError = 0; 115 const int kGenericError = -1; 116 117 int tool_main(int argc, char** argv); 118 int tool_main(int argc, char** argv) { 119 DiffMetricProc diffProc = compute_diff_pmcolor; 120 121 // Maximum error tolerated in any one color channel in any one pixel before 122 // a difference is reported. 123 int colorThreshold = 0; 124 SkString baseFile; 125 SkString baseLabel; 126 SkString comparisonFile; 127 SkString comparisonLabel; 128 SkString outputDir; 129 130 bool listFilenames = false; 131 132 bool failOnResultType[DiffRecord::kResultCount]; 133 for (int i = 0; i < DiffRecord::kResultCount; i++) { 134 failOnResultType[i] = false; 135 } 136 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; 141 } 142 } 143 144 int i; 145 int numUnflaggedArguments = 0; 146 int numLabelArguments = 0; 147 for (i = 1; i < argc; i++) { 148 if (!strcmp(argv[i], "--failonresult")) { 149 if (argc == ++i) { 150 SkDebugf("failonresult expects one argument.\n"); 151 continue; 152 } 153 DiffRecord::Result type = DiffRecord::getResultByName(argv[i]); 154 if (type != DiffRecord::kResultCount) { 155 failOnResultType[type] = true; 156 } else { 157 SkDebugf("ignoring unrecognized result <%s>\n", argv[i]); 158 } 159 continue; 160 } 161 if (!strcmp(argv[i], "--failonstatus")) { 162 if (argc == ++i) { 163 SkDebugf("failonstatus missing base status.\n"); 164 continue; 165 } 166 bool baseStatuses[DiffResource::kStatusCount]; 167 if (!DiffResource::getMatchingStatuses(argv[i], baseStatuses)) { 168 SkDebugf("unrecognized base status <%s>\n", argv[i]); 169 } 170 171 if (argc == ++i) { 172 SkDebugf("failonstatus missing comparison status.\n"); 173 continue; 174 } 175 bool comparisonStatuses[DiffResource::kStatusCount]; 176 if (!DiffResource::getMatchingStatuses(argv[i], comparisonStatuses)) { 177 SkDebugf("unrecognized comarison status <%s>\n", argv[i]); 178 } 179 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]; 184 } 185 } 186 continue; 187 } 188 if (!strcmp(argv[i], "--help")) { 189 usage(argv[0]); 190 return kNoError; 191 } 192 if (!strcmp(argv[i], "--listfilenames")) { 193 listFilenames = true; 194 continue; 195 } 196 if (!strcmp(argv[i], "--outputdir")) { 197 if (argc == ++i) { 198 SkDebugf("outputdir expects one argument.\n"); 199 continue; 200 } 201 outputDir.set(argv[i]); 202 continue; 203 } 204 if (!strcmp(argv[i], "--threshold")) { 205 colorThreshold = atoi(argv[++i]); 206 continue; 207 } 208 if (!strcmp(argv[i], "-u")) { 209 //we don't produce unified diffs, ignore parameter to work with svn diff 210 continue; 211 } 212 if (!strcmp(argv[i], "-L")) { 213 if (argc == ++i) { 214 SkDebugf("label expects one argument.\n"); 215 continue; 216 } 217 switch (numLabelArguments++) { 218 case 0: 219 baseLabel.set(argv[i]); 220 continue; 221 case 1: 222 comparisonLabel.set(argv[i]); 223 continue; 224 default: 225 SkDebugf("extra label argument <%s>\n", argv[i]); 226 usage(argv[0]); 227 return kGenericError; 228 } 229 continue; 230 } 231 if (argv[i][0] != '-') { 232 switch (numUnflaggedArguments++) { 233 case 0: 234 baseFile.set(argv[i]); 235 continue; 236 case 1: 237 comparisonFile.set(argv[i]); 238 continue; 239 default: 240 SkDebugf("extra unflagged argument <%s>\n", argv[i]); 241 usage(argv[0]); 242 return kGenericError; 243 } 244 } 245 246 SkDebugf("Unrecognized argument <%s>\n", argv[i]); 247 usage(argv[0]); 248 return kGenericError; 249 } 250 251 if (numUnflaggedArguments != 2) { 252 usage(argv[0]); 253 return kGenericError; 254 } 255 256 if (listFilenames) { 257 printf("Base file is [%s]\n", baseFile.c_str()); 258 } 259 260 if (listFilenames) { 261 printf("Comparison file is [%s]\n", comparisonFile.c_str()); 262 } 263 264 if (outputDir.isEmpty()) { 265 if (listFilenames) { 266 printf("Not writing any diffs. No output dir specified.\n"); 267 } 268 } else { 269 if (!outputDir.endsWith(PATH_DIV_STR)) { 270 outputDir.append(PATH_DIV_STR); 271 } 272 if (listFilenames) { 273 printf("Writing diffs. Output dir is [%s]\n", outputDir.c_str()); 274 } 275 } 276 277 // Some obscure documentation about diff/patch labels: 278 // 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 282 // 283 // Svn diff --diff-cmd provides labels of the form: <filename><tab><revision> 284 // 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)" 288 // 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)" 291 // 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 298 // 299 // Patch decodes: 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>). 304 // 305 // The filename /dev/null means the file does not exist (used in adds and deletes). 306 307 // Considering the above, skimagediff will consider the contents of a -L parameter as 308 // <filename>(\t<specifier>)? 309 SkString outputFile; 310 311 if (baseLabel.isEmpty()) { 312 baseLabel.set(baseFile); 313 outputFile = baseLabel; 314 } else { 315 const char* baseLabelCstr = baseLabel.c_str(); 316 const char* tab = strchr(baseLabelCstr, '\t'); 317 if (NULL == tab) { 318 outputFile = baseLabel; 319 } else { 320 outputFile.set(baseLabelCstr, tab - baseLabelCstr); 321 } 322 } 323 if (comparisonLabel.isEmpty()) { 324 comparisonLabel.set(comparisonFile); 325 } 326 printf("Base: %s\n", baseLabel.c_str()); 327 printf("Comparison: %s\n", comparisonLabel.c_str()); 328 329 DiffRecord dr; 330 create_diff_images(diffProc, colorThreshold, baseFile, comparisonFile, outputDir, outputFile, 331 &dr); 332 333 if (DiffResource::isStatusFailed(dr.fBase.fStatus)) { 334 printf("Base %s.\n", DiffResource::getStatusDescription(dr.fBase.fStatus)); 335 } 336 if (DiffResource::isStatusFailed(dr.fComparison.fStatus)) { 337 printf("Comparison %s.\n", DiffResource::getStatusDescription(dr.fComparison.fStatus)); 338 } 339 printf("Base and Comparison %s.\n", DiffRecord::getResultDescription(dr.fResult)); 340 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())); 348 } 349 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, 356 dr.fMaxMismatchG, 357 dr.fMaxMismatchB)); 358 printf("\n"); 359 } 360 printf("\n"); 361 362 int num_failing_results = 0; 363 if (failOnResultType[dr.fResult]) { 364 ++num_failing_results; 365 } 366 if (failOnStatusType[dr.fBase.fStatus][dr.fComparison.fStatus]) { 367 ++num_failing_results; 368 } 369 370 return num_failing_results; 371 } 372 373 #if !defined SK_BUILD_FOR_IOS 374 int main(int argc, char * const argv[]) { 375 return tool_main(argc, (char**) argv); 376 } 377 #endif 378