Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 /* | 1 /* |
| 2 * Copyright 2011 Google Inc. | 2 * Copyright 2011 Google Inc. |
| 3 * | 3 * |
| 4 * Use of this source code is governed by a BSD-style license that can be | 4 * Use of this source code is governed by a BSD-style license that can be |
| 5 * found in the LICENSE file. | 5 * found in the LICENSE file. |
| 6 */ | 6 */ |
| 7 | 7 |
| 8 #include "gm_expectations.h" | |
| 8 #include "SkBitmap.h" | 9 #include "SkBitmap.h" |
| 10 #include "SkBitmapHasher.h" | |
| 9 #include "SkColorPriv.h" | 11 #include "SkColorPriv.h" |
| 10 #include "SkCommandLineFlags.h" | 12 #include "SkCommandLineFlags.h" |
| 11 #include "SkData.h" | 13 #include "SkData.h" |
| 12 #include "SkGraphics.h" | 14 #include "SkGraphics.h" |
| 13 #include "SkImageDecoder.h" | 15 #include "SkImageDecoder.h" |
| 14 #include "SkImageEncoder.h" | 16 #include "SkImageEncoder.h" |
| 15 #include "SkOSFile.h" | 17 #include "SkOSFile.h" |
| 16 #include "SkRandom.h" | 18 #include "SkRandom.h" |
| 17 #include "SkStream.h" | 19 #include "SkStream.h" |
| 18 #include "SkTArray.h" | 20 #include "SkTArray.h" |
| 19 #include "SkTemplates.h" | 21 #include "SkTemplates.h" |
| 20 | 22 |
| 23 DEFINE_string(createExpectationsPath, "", "Path to write JSON expectations."); | |
| 21 DEFINE_string2(readPath, r, "", "Folder(s) and files to decode images. Required. "); | 24 DEFINE_string2(readPath, r, "", "Folder(s) and files to decode images. Required. "); |
| 25 DEFINE_string(readExpectationsPath, "", "Path to read JSON expectations from."); | |
| 22 DEFINE_string2(writePath, w, "", "Write rendered images into this directory."); | 26 DEFINE_string2(writePath, w, "", "Write rendered images into this directory."); |
| 23 DEFINE_bool(reencode, true, "Reencode the images to test encoding."); | 27 DEFINE_bool(reencode, true, "Reencode the images to test encoding."); |
| 24 DEFINE_bool(testSubsetDecoding, true, "Test decoding subsets of images."); | 28 DEFINE_bool(testSubsetDecoding, true, "Test decoding subsets of images."); |
| 25 | 29 |
| 26 struct Format { | 30 struct Format { |
| 27 SkImageEncoder::Type fType; | 31 SkImageEncoder::Type fType; |
| 28 SkImageDecoder::Format fFormat; | 32 SkImageDecoder::Format fFormat; |
| 29 const char* fSuffix; | 33 const char* fSuffix; |
| 30 }; | 34 }; |
| 31 | 35 |
| (...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 90 // Store the names of the filenames to report later which ones failed, succeeded , and were | 94 // Store the names of the filenames to report later which ones failed, succeeded , and were |
| 91 // invalid. | 95 // invalid. |
| 92 static SkTArray<SkString, false> gInvalidStreams; | 96 static SkTArray<SkString, false> gInvalidStreams; |
| 93 static SkTArray<SkString, false> gMissingCodecs; | 97 static SkTArray<SkString, false> gMissingCodecs; |
| 94 static SkTArray<SkString, false> gDecodeFailures; | 98 static SkTArray<SkString, false> gDecodeFailures; |
| 95 static SkTArray<SkString, false> gEncodeFailures; | 99 static SkTArray<SkString, false> gEncodeFailures; |
| 96 static SkTArray<SkString, false> gSuccessfulDecodes; | 100 static SkTArray<SkString, false> gSuccessfulDecodes; |
| 97 static SkTArray<SkString, false> gSuccessfulSubsetDecodes; | 101 static SkTArray<SkString, false> gSuccessfulSubsetDecodes; |
| 98 static SkTArray<SkString, false> gFailedSubsetDecodes; | 102 static SkTArray<SkString, false> gFailedSubsetDecodes; |
| 99 | 103 |
| 104 // Expections read from a file specified by readExpectationsPath. The expectatio ns must have been | |
| 105 // previously written using createExpectationsPath. | |
| 106 SkAutoTUnref<skiagm::JsonExpectationsSource> gJsonExpectations; | |
| 107 | |
| 100 static bool write_bitmap(const char outName[], SkBitmap* bm) { | 108 static bool write_bitmap(const char outName[], SkBitmap* bm) { |
| 101 SkBitmap bitmap8888; | 109 SkBitmap bitmap8888; |
| 102 if (SkBitmap::kARGB_8888_Config != bm->config()) { | 110 if (SkBitmap::kARGB_8888_Config != bm->config()) { |
| 103 if (!bm->copyTo(&bitmap8888, SkBitmap::kARGB_8888_Config)) { | 111 if (!bm->copyTo(&bitmap8888, SkBitmap::kARGB_8888_Config)) { |
| 104 return false; | 112 return false; |
| 105 } | 113 } |
| 106 bm = &bitmap8888; | 114 bm = &bitmap8888; |
| 107 } | 115 } |
| 108 // FIXME: This forces all pixels to be opaque, like the many implementations | 116 // FIXME: This forces all pixels to be opaque, like the many implementations |
| 109 // of force_all_opaque. These should be unified if they cannot be eliminated . | 117 // of force_all_opaque. These should be unified if they cannot be eliminated . |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 150 rect.fTop--; | 158 rect.fTop--; |
| 151 } else { | 159 } else { |
| 152 rect.fBottom++; | 160 rect.fBottom++; |
| 153 // Again, this must be in range. | 161 // Again, this must be in range. |
| 154 SkASSERT(rect.fBottom < maxY); | 162 SkASSERT(rect.fBottom < maxY); |
| 155 } | 163 } |
| 156 } | 164 } |
| 157 return rect; | 165 return rect; |
| 158 } | 166 } |
| 159 | 167 |
| 168 // Stored expectations to be written to a file createExpectationsPath is specifi ed. | |
|
epoger
2013/05/08 17:24:48
"written to a file createExpectationsPath" -> "wri
scroggo
2013/05/08 18:46:33
Done.
| |
| 169 static Json::Value gExpectationsToWrite; | |
| 170 | |
| 171 /** | |
| 172 * If expectations are to be recorded, record the expected checksum of bitmap i nto global | |
| 173 * expectations array. | |
| 174 */ | |
| 175 static void write_expectations(const SkBitmap& bitmap, const char* filename) { | |
| 176 if (!FLAGS_createExpectationsPath.isEmpty()) { | |
| 177 // Creates an Expectations object, and add it to the list to write. | |
| 178 skiagm::Expectations expectation(bitmap); | |
| 179 Json::Value value = expectation.asJsonValue(); | |
| 180 gExpectationsToWrite[filename] = value; | |
| 181 } | |
| 182 } | |
| 183 | |
| 184 /** | |
| 185 * Compare against an expectation for this filename, if there is one. | |
| 186 * @param bitmap SkBitmap to compare to the expected value. | |
| 187 * @param filename String used to find the expected value. | |
| 188 * @return bool True if the bitmap matched the expectation, or if there was no expectation. False | |
| 189 * if there was an expecation that the bitmap did not match, or if an expec tation could not be | |
| 190 * computed from an expectation. | |
| 191 */ | |
| 192 static bool compare_to_expectations_if_necessary(const SkBitmap& bitmap, const c har* filename, | |
| 193 SkTArray<SkString, false>* fail ureArray) { | |
|
epoger
2013/05/08 17:24:48
could you pass failureArray by reference instead,
scroggo
2013/05/08 18:46:33
That makes sense to me, but according to our codin
| |
| 194 if (NULL == gJsonExpectations.get()) { | |
| 195 return true; | |
| 196 } | |
| 197 skiagm::Expectations jsExpectation = gJsonExpectations->get(filename); | |
| 198 if (jsExpectation.empty()) { | |
| 199 return true; | |
| 200 } | |
| 201 SkHashDigest checksum; | |
| 202 if (SkBitmapHasher::ComputeDigest(bitmap, &checksum)) { | |
|
epoger
2013/05/08 17:24:48
I think it would be a bit easier to follow if you
scroggo
2013/05/08 18:46:33
Yes, that is much clearer! Done!
| |
| 203 if (jsExpectation.match(checksum)) { | |
| 204 return true; | |
| 205 } else { | |
| 206 failureArray->push_back().printf("decoded %s, but the result does no t match " | |
| 207 "expectations.", | |
| 208 filename); | |
| 209 return false; | |
| 210 } | |
| 211 } else { | |
| 212 failureArray->push_back().printf("decoded %s, but could not create a che cksum.", filename); | |
| 213 return false; | |
| 214 } | |
| 215 } | |
| 216 | |
| 160 static void decodeFileAndWrite(const char srcPath[], const SkString* writePath) { | 217 static void decodeFileAndWrite(const char srcPath[], const SkString* writePath) { |
| 161 SkBitmap bitmap; | 218 SkBitmap bitmap; |
| 162 SkFILEStream stream(srcPath); | 219 SkFILEStream stream(srcPath); |
| 163 if (!stream.isValid()) { | 220 if (!stream.isValid()) { |
| 164 gInvalidStreams.push_back().set(srcPath); | 221 gInvalidStreams.push_back().set(srcPath); |
| 165 return; | 222 return; |
| 166 } | 223 } |
| 167 | 224 |
| 168 SkImageDecoder* codec = SkImageDecoder::Factory(&stream); | 225 SkImageDecoder* codec = SkImageDecoder::Factory(&stream); |
| 169 if (NULL == codec) { | 226 if (NULL == codec) { |
| 170 gMissingCodecs.push_back().set(srcPath); | 227 gMissingCodecs.push_back().set(srcPath); |
| 171 return; | 228 return; |
| 172 } | 229 } |
| 173 | 230 |
| 174 SkAutoTDelete<SkImageDecoder> ad(codec); | 231 SkAutoTDelete<SkImageDecoder> ad(codec); |
| 175 | 232 |
| 176 stream.rewind(); | 233 stream.rewind(); |
| 177 if (!codec->decode(&stream, &bitmap, SkBitmap::kARGB_8888_Config, | 234 if (!codec->decode(&stream, &bitmap, SkBitmap::kARGB_8888_Config, |
| 178 SkImageDecoder::kDecodePixels_Mode)) { | 235 SkImageDecoder::kDecodePixels_Mode)) { |
| 179 gDecodeFailures.push_back().set(srcPath); | 236 gDecodeFailures.push_back().set(srcPath); |
| 180 return; | 237 return; |
| 181 } | 238 } |
| 182 | 239 |
| 183 gSuccessfulDecodes.push_back().printf("%s [%d %d]", srcPath, bitmap.width(), bitmap.height()); | 240 // Create a string representing just the filename itself, for use in json ex pectations. |
| 241 const char* filename = strrchr(srcPath, SkPATH_SEPARATOR); | |
|
epoger
2013/05/08 17:24:48
Maybe split this out into its own SkBasename() met
scroggo
2013/05/08 18:46:33
Moved into its own method. Sure enough, other file
| |
| 242 if (NULL == filename || ++filename == '\0') { | |
| 243 filename = srcPath; | |
| 244 } | |
| 245 | |
| 246 if (compare_to_expectations_if_necessary(bitmap, filename, &gDecodeFailures) ) { | |
| 247 gSuccessfulDecodes.push_back().printf("%s [%d %d]", srcPath, bitmap.widt h(), | |
| 248 bitmap.height()); | |
| 249 } | |
| 250 | |
| 251 write_expectations(bitmap, filename); | |
| 184 | 252 |
| 185 if (FLAGS_testSubsetDecoding) { | 253 if (FLAGS_testSubsetDecoding) { |
| 186 SkDEBUGCODE(bool couldRewind =) stream.rewind(); | 254 SkDEBUGCODE(bool couldRewind =) stream.rewind(); |
| 187 SkASSERT(couldRewind); | 255 SkASSERT(couldRewind); |
| 188 int width, height; | 256 int width, height; |
| 189 // Build the tile index for decoding subsets. If the image is 1x1, skip subset | 257 // Build the tile index for decoding subsets. If the image is 1x1, skip subset |
| 190 // decoding since there are no smaller subsets. | 258 // decoding since there are no smaller subsets. |
| 191 if (codec->buildTileIndex(&stream, &width, &height) && width > 1 && heig ht > 1) { | 259 if (codec->buildTileIndex(&stream, &width, &height) && width > 1 && heig ht > 1) { |
| 192 SkASSERT(bitmap.width() == width && bitmap.height() == height); | 260 SkASSERT(bitmap.width() == width && bitmap.height() == height); |
| 193 // Call decodeSubset multiple times: | 261 // Call decodeSubset multiple times: |
| 194 SkRandom rand(0); | 262 SkRandom rand(0); |
| 195 for (int i = 0; i < 5; i++) { | 263 for (int i = 0; i < 5; i++) { |
| 196 SkBitmap bitmapFromDecodeSubset; | 264 SkBitmap bitmapFromDecodeSubset; |
| 197 // FIXME: Come up with a more representative set of rectangles. | 265 // FIXME: Come up with a more representative set of rectangles. |
| 198 SkIRect rect = generate_random_rect(&rand, width, height); | 266 SkIRect rect = generate_random_rect(&rand, width, height); |
| 199 SkString subsetDim = SkStringPrintf("[%d,%d,%d,%d]", rect.fLeft, rect.fTop, | 267 SkString subsetDim = SkStringPrintf("[%d,%d,%d,%d]", rect.fLeft, rect.fTop, |
| 200 rect.fRight, rect.fBottom); | 268 rect.fRight, rect.fBottom); |
| 201 if (codec->decodeSubset(&bitmapFromDecodeSubset, rect, SkBitmap: :kNo_Config)) { | 269 if (codec->decodeSubset(&bitmapFromDecodeSubset, rect, SkBitmap: :kNo_Config)) { |
| 202 gSuccessfulSubsetDecodes.push_back().printf("Decoded subset %s from %s", | 270 SkString subsetName = SkStringPrintf("%s_%s", filename, subs etDim.c_str()); |
| 203 subsetDim.c_str(), src Path); | 271 if (compare_to_expectations_if_necessary(bitmapFromDecodeSub set, |
| 272 subsetName.c_str(), | |
| 273 &gFailedSubsetDecod es)) { | |
| 274 gSuccessfulSubsetDecodes.push_back().printf("Decoded sub set %s from %s", | |
| 275 subsetDim.c_str(), srcPath); | |
| 276 } | |
| 277 | |
| 278 write_expectations(bitmapFromDecodeSubset, subsetName.c_str( )); | |
| 279 | |
| 204 if (writePath != NULL) { | 280 if (writePath != NULL) { |
| 205 // Write the region to a file whose name includes the di mensions. | 281 // Write the region to a file whose name includes the di mensions. |
| 206 SkString suffix = SkStringPrintf("_%s.png", subsetDim.c_ str()); | 282 SkString suffix = SkStringPrintf("_%s.png", subsetDim.c_ str()); |
| 207 SkString outPath; | 283 SkString outPath; |
| 208 make_outname(&outPath, writePath->c_str(), srcPath, suff ix.c_str()); | 284 make_outname(&outPath, writePath->c_str(), srcPath, suff ix.c_str()); |
| 209 SkDEBUGCODE(bool success =) | 285 SkDEBUGCODE(bool success =) |
| 210 write_bitmap(outPath.c_str(), &bitmapFromDecodeSubset); | 286 write_bitmap(outPath.c_str(), &bitmapFromDecodeSubset); |
| 211 SkASSERT(success); | 287 SkASSERT(success); |
| 212 gSuccessfulSubsetDecodes.push_back().printf("\twrote %s" , outPath.c_str()); | 288 gSuccessfulSubsetDecodes.push_back().printf("\twrote %s" , outPath.c_str()); |
| 213 // Also use extractSubset from the original for visual c omparison. | 289 // Also use extractSubset from the original for visual c omparison. |
| (...skipping 114 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 328 SkCommandLineFlags::Parse(argc, argv); | 404 SkCommandLineFlags::Parse(argc, argv); |
| 329 | 405 |
| 330 if (FLAGS_readPath.count() < 1) { | 406 if (FLAGS_readPath.count() < 1) { |
| 331 SkDebugf("Folder(s) or image(s) to decode are required.\n"); | 407 SkDebugf("Folder(s) or image(s) to decode are required.\n"); |
| 332 return -1; | 408 return -1; |
| 333 } | 409 } |
| 334 | 410 |
| 335 | 411 |
| 336 SkAutoGraphics ag; | 412 SkAutoGraphics ag; |
| 337 | 413 |
| 414 if (!FLAGS_readExpectationsPath.isEmpty()) { | |
| 415 gJsonExpectations.reset(SkNEW_ARGS(skiagm::JsonExpectationsSource, | |
| 416 (FLAGS_readExpectationsPath[0]))); | |
| 417 } | |
| 418 | |
| 338 SkString outDir; | 419 SkString outDir; |
| 339 SkString* outDirPtr; | 420 SkString* outDirPtr; |
| 340 | 421 |
| 341 if (FLAGS_writePath.count() == 1) { | 422 if (FLAGS_writePath.count() == 1) { |
| 342 outDir.set(FLAGS_writePath[0]); | 423 outDir.set(FLAGS_writePath[0]); |
| 343 if (outDir.c_str()[outDir.size() - 1] != '/') { | 424 if (outDir.c_str()[outDir.size() - 1] != SkPATH_SEPARATOR) { |
|
scroggo
2013/05/08 18:46:33
Also moved into its own function.
| |
| 344 outDir.append("/"); | 425 outDir.append(&SkPATH_SEPARATOR); |
| 345 } | 426 } |
| 346 outDirPtr = &outDir; | 427 outDirPtr = &outDir; |
| 347 } else { | 428 } else { |
| 348 outDirPtr = NULL; | 429 outDirPtr = NULL; |
| 349 } | 430 } |
| 350 | 431 |
| 351 for (int i = 0; i < FLAGS_readPath.count(); i++) { | 432 for (int i = 0; i < FLAGS_readPath.count(); i++) { |
| 352 if (strlen(FLAGS_readPath[i]) < 1) { | 433 if (strlen(FLAGS_readPath[i]) < 1) { |
| 353 break; | 434 break; |
| 354 } | 435 } |
| 355 SkOSFile::Iter iter(FLAGS_readPath[i]); | 436 SkOSFile::Iter iter(FLAGS_readPath[i]); |
| 356 SkString filename; | 437 SkString filename; |
| 357 if (iter.next(&filename)) { | 438 if (iter.next(&filename)) { |
| 358 SkString directory(FLAGS_readPath[i]); | 439 SkString directory(FLAGS_readPath[i]); |
| 359 if (directory[directory.size() - 1] != '/') { | 440 if (directory[directory.size() - 1] != SkPATH_SEPARATOR) { |
| 360 directory.append("/"); | 441 directory.append(&SkPATH_SEPARATOR); |
| 361 } | 442 } |
| 362 do { | 443 do { |
| 363 SkString fullname(directory); | 444 SkString fullname(directory); |
| 364 fullname.append(filename); | 445 fullname.append(filename); |
| 365 decodeFileAndWrite(fullname.c_str(), outDirPtr); | 446 decodeFileAndWrite(fullname.c_str(), outDirPtr); |
| 366 } while (iter.next(&filename)); | 447 } while (iter.next(&filename)); |
| 367 } else { | 448 } else { |
| 368 decodeFileAndWrite(FLAGS_readPath[i], outDirPtr); | 449 decodeFileAndWrite(FLAGS_readPath[i], outDirPtr); |
| 369 } | 450 } |
| 370 } | 451 } |
| 371 | 452 |
| 453 if (!FLAGS_createExpectationsPath.isEmpty()) { | |
| 454 // Use an empty value for everything besides expectations, since the rea der only cares | |
| 455 // about the expectations. | |
| 456 Json::Value nullValue; | |
| 457 Json::Value root = skiagm::CreateJsonTree(gExpectationsToWrite, nullValu e, nullValue, | |
| 458 nullValue, nullValue); | |
| 459 std::string jsonStdString = root.toStyledString(); | |
| 460 SkString path = SkStringPrintf("%s%cresults.json", FLAGS_createExpectati onsPath[0], | |
| 461 SkPATH_SEPARATOR); | |
| 462 SkFILEWStream stream(path.c_str()); | |
| 463 stream.write(jsonStdString.c_str(), jsonStdString.length()); | |
| 464 } | |
| 372 // Add some space, since codecs may print warnings without newline. | 465 // Add some space, since codecs may print warnings without newline. |
| 373 SkDebugf("\n\n"); | 466 SkDebugf("\n\n"); |
| 374 | 467 |
| 375 bool failed = print_strings("Invalid files", gInvalidStreams); | 468 bool failed = print_strings("Invalid files", gInvalidStreams); |
| 376 failed |= print_strings("Missing codec", gMissingCodecs); | 469 failed |= print_strings("Missing codec", gMissingCodecs); |
| 377 failed |= print_strings("Failed to decode", gDecodeFailures); | 470 failed |= print_strings("Failed to decode", gDecodeFailures); |
| 378 failed |= print_strings("Failed to encode", gEncodeFailures); | 471 failed |= print_strings("Failed to encode", gEncodeFailures); |
| 379 print_strings("Decoded", gSuccessfulDecodes); | 472 print_strings("Decoded", gSuccessfulDecodes); |
| 380 | 473 |
| 381 if (FLAGS_testSubsetDecoding) { | 474 if (FLAGS_testSubsetDecoding) { |
| 382 failed |= print_strings("Failed subset decodes", gFailedSubsetDecodes); | 475 failed |= print_strings("Failed subset decodes", gFailedSubsetDecodes); |
| 383 print_strings("Decoded subsets", gSuccessfulSubsetDecodes); | 476 print_strings("Decoded subsets", gSuccessfulSubsetDecodes); |
| 384 } | 477 } |
| 385 | 478 |
| 386 return failed ? -1 : 0; | 479 return failed ? -1 : 0; |
| 387 } | 480 } |
| 388 | 481 |
| 389 #if !defined SK_BUILD_FOR_IOS | 482 #if !defined SK_BUILD_FOR_IOS |
| 390 int main(int argc, char * const argv[]) { | 483 int main(int argc, char * const argv[]) { |
| 391 return tool_main(argc, (char**) argv); | 484 return tool_main(argc, (char**) argv); |
| 392 } | 485 } |
| 393 #endif | 486 #endif |
| OLD | NEW |