| OLD | NEW |
| (Empty) |
| 1 /* | |
| 2 * Copyright 2011 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 | |
| 8 #include "gm_expectations.h" | |
| 9 #include "SkBitmap.h" | |
| 10 #include "SkColorPriv.h" | |
| 11 #include "SkCommandLineFlags.h" | |
| 12 #include "SkData.h" | |
| 13 #include "SkForceLinking.h" | |
| 14 #include "SkGraphics.h" | |
| 15 #include "SkImageDecoder.h" | |
| 16 #include "SkImageEncoder.h" | |
| 17 #include "SkOSFile.h" | |
| 18 #include "SkRandom.h" | |
| 19 #include "SkStream.h" | |
| 20 #include "SkTArray.h" | |
| 21 #include "SkTemplates.h" | |
| 22 | |
| 23 __SK_FORCE_IMAGE_DECODER_LINKING; | |
| 24 | |
| 25 DEFINE_string(config, "None", "Preferred config to decode into. [None|8888|565|A
8]"); | |
| 26 DEFINE_string(createExpectationsPath, "", "Path to write JSON expectations."); | |
| 27 DEFINE_string(mismatchPath, "", "Folder to write mismatched images to."); | |
| 28 DEFINE_string2(readPath, r, "", "Folder(s) and files to decode images. Required.
"); | |
| 29 DEFINE_string(readExpectationsPath, "", "Path to read JSON expectations from."); | |
| 30 DEFINE_bool(reencode, true, "Reencode the images to test encoding."); | |
| 31 DEFINE_int32(sampleSize, 1, "Set the sampleSize for decoding."); | |
| 32 DEFINE_bool(skip, false, "Skip writing zeroes."); | |
| 33 DEFINE_bool(testSubsetDecoding, true, "Test decoding subsets of images."); | |
| 34 DEFINE_bool(writeChecksumBasedFilenames, false, "When writing out actual images
, use checksum-" | |
| 35 "based filenames, as rebaseline.py will use when downloading them fr
om Google Storage"); | |
| 36 DEFINE_string2(writePath, w, "", "Write rendered images into this directory."); | |
| 37 DEFINE_bool(unpremul, false, "Require unpremultiplied colors."); | |
| 38 | |
| 39 struct Format { | |
| 40 SkImageEncoder::Type fType; | |
| 41 SkImageDecoder::Format fFormat; | |
| 42 const char* fSuffix; | |
| 43 }; | |
| 44 | |
| 45 static const Format gFormats[] = { | |
| 46 { SkImageEncoder::kBMP_Type, SkImageDecoder::kBMP_Format, ".bmp" }, | |
| 47 { SkImageEncoder::kGIF_Type, SkImageDecoder::kGIF_Format, ".gif" }, | |
| 48 { SkImageEncoder::kICO_Type, SkImageDecoder::kICO_Format, ".ico" }, | |
| 49 { SkImageEncoder::kJPEG_Type, SkImageDecoder::kJPEG_Format, ".jpg" }, | |
| 50 { SkImageEncoder::kPNG_Type, SkImageDecoder::kPNG_Format, ".png" }, | |
| 51 { SkImageEncoder::kWBMP_Type, SkImageDecoder::kWBMP_Format, ".wbmp" }, | |
| 52 { SkImageEncoder::kWEBP_Type, SkImageDecoder::kWEBP_Format, ".webp" } | |
| 53 }; | |
| 54 | |
| 55 static SkImageEncoder::Type format_to_type(SkImageDecoder::Format format) { | |
| 56 for (size_t i = 0; i < SK_ARRAY_COUNT(gFormats); i++) { | |
| 57 if (gFormats[i].fFormat == format) { | |
| 58 return gFormats[i].fType; | |
| 59 } | |
| 60 } | |
| 61 return SkImageEncoder::kUnknown_Type; | |
| 62 } | |
| 63 | |
| 64 static const char* suffix_for_type(SkImageEncoder::Type type) { | |
| 65 for (size_t i = 0; i < SK_ARRAY_COUNT(gFormats); i++) { | |
| 66 if (gFormats[i].fType == type) { | |
| 67 return gFormats[i].fSuffix; | |
| 68 } | |
| 69 } | |
| 70 return ""; | |
| 71 } | |
| 72 | |
| 73 static SkImageDecoder::Format guess_format_from_suffix(const char suffix[]) { | |
| 74 for (size_t i = 0; i < SK_ARRAY_COUNT(gFormats); i++) { | |
| 75 if (strcmp(suffix, gFormats[i].fSuffix) == 0) { | |
| 76 return gFormats[i].fFormat; | |
| 77 } | |
| 78 } | |
| 79 return SkImageDecoder::kUnknown_Format; | |
| 80 } | |
| 81 | |
| 82 static void make_outname(SkString* dst, const char outDir[], const char src[], | |
| 83 const char suffix[]) { | |
| 84 SkString basename = SkOSPath::Basename(src); | |
| 85 dst->set(SkOSPath::Join(outDir, basename.c_str())); | |
| 86 dst->append(suffix); | |
| 87 } | |
| 88 | |
| 89 // Store the names of the filenames to report later which ones failed, succeeded
, and were | |
| 90 // invalid. | |
| 91 // FIXME: Add more arrays, for more specific types of errors, and make the outpu
t simpler. | |
| 92 // If each array holds one type of error, the output can change from: | |
| 93 // | |
| 94 // Failures: | |
| 95 // <image> failed for such and such reason | |
| 96 // <image> failed for some different reason | |
| 97 // | |
| 98 // to: | |
| 99 // | |
| 100 // Such and such failures: | |
| 101 // <image> | |
| 102 // | |
| 103 // Different reason failures: | |
| 104 // <image> | |
| 105 // | |
| 106 static SkTArray<SkString, false> gInvalidStreams; | |
| 107 static SkTArray<SkString, false> gMissingCodecs; | |
| 108 static SkTArray<SkString, false> gDecodeFailures; | |
| 109 static SkTArray<SkString, false> gEncodeFailures; | |
| 110 static SkTArray<SkString, false> gSuccessfulDecodes; | |
| 111 static SkTArray<SkString, false> gSuccessfulSubsetDecodes; | |
| 112 static SkTArray<SkString, false> gFailedSubsetDecodes; | |
| 113 // Files/subsets that do not have expectations. Not reported as a failure of the
test so | |
| 114 // the bots will not turn red with each new image test. | |
| 115 static SkTArray<SkString, false> gMissingExpectations; | |
| 116 static SkTArray<SkString, false> gMissingSubsetExpectations; | |
| 117 // For files that are expected to fail. | |
| 118 static SkTArray<SkString, false> gKnownFailures; | |
| 119 static SkTArray<SkString, false> gKnownSubsetFailures; | |
| 120 | |
| 121 static SkColorType gPrefColorType(kUnknown_SkColorType); | |
| 122 | |
| 123 // Expections read from a file specified by readExpectationsPath. The expectatio
ns must have been | |
| 124 // previously written using createExpectationsPath. | |
| 125 SkAutoTUnref<skiagm::JsonExpectationsSource> gJsonExpectations; | |
| 126 | |
| 127 /** | |
| 128 * Encode the bitmap to a file, written one of two ways, depending on | |
| 129 * FLAGS_writeChecksumBasedFilenames. If true, the final image will be | |
| 130 * written to: | |
| 131 * outDir/hashType/src/digestValue.png | |
| 132 * If false, the final image will be written out to: | |
| 133 * outDir/src.png | |
| 134 * The function returns whether the file was successfully written. | |
| 135 */ | |
| 136 static bool write_bitmap(const char outDir[], const char src[], | |
| 137 const skiagm::BitmapAndDigest& bitmapAndDigest) { | |
| 138 SkString filename; | |
| 139 if (FLAGS_writeChecksumBasedFilenames) { | |
| 140 // First create the directory for the hashtype. | |
| 141 const SkString hashType = bitmapAndDigest.fDigest.getHashType(); | |
| 142 const SkString hashDir = SkOSPath::Join(outDir, hashType.c_str()); | |
| 143 if (!sk_mkdir(hashDir.c_str())) { | |
| 144 return false; | |
| 145 } | |
| 146 | |
| 147 // Now create the name of the folder specific to this image. | |
| 148 SkString basename = SkOSPath::Basename(src); | |
| 149 const SkString imageDir = SkOSPath::Join(hashDir.c_str(), basename.c_str
()); | |
| 150 if (!sk_mkdir(imageDir.c_str())) { | |
| 151 return false; | |
| 152 } | |
| 153 | |
| 154 // Name the file <digest>.png | |
| 155 SkString checksumBasedName = bitmapAndDigest.fDigest.getDigestValue(); | |
| 156 checksumBasedName.append(".png"); | |
| 157 | |
| 158 filename = SkOSPath::Join(imageDir.c_str(), checksumBasedName.c_str()); | |
| 159 } else { | |
| 160 make_outname(&filename, outDir, src, ".png"); | |
| 161 } | |
| 162 | |
| 163 const SkBitmap& bm = bitmapAndDigest.fBitmap; | |
| 164 if (SkImageEncoder::EncodeFile(filename.c_str(), bm, SkImageEncoder::kPNG_Ty
pe, 100)) { | |
| 165 return true; | |
| 166 } | |
| 167 | |
| 168 if (bm.colorType() == kN32_SkColorType) { | |
| 169 // First attempt at encoding failed, and the bitmap was already 8888. Ma
king | |
| 170 // a copy is not going to help. | |
| 171 return false; | |
| 172 } | |
| 173 | |
| 174 // Encoding failed. Copy to 8888 and try again. | |
| 175 SkBitmap bm8888; | |
| 176 if (!bm.copyTo(&bm8888, kN32_SkColorType)) { | |
| 177 return false; | |
| 178 } | |
| 179 return SkImageEncoder::EncodeFile(filename.c_str(), bm8888, SkImageEncoder::
kPNG_Type, 100); | |
| 180 } | |
| 181 | |
| 182 /** | |
| 183 * Return a random SkIRect inside the range specified. | |
| 184 * @param rand Random number generator. | |
| 185 * @param maxX Exclusive maximum x-coordinate. SkIRect's fLeft and fRight will
be | |
| 186 * in the range [0, maxX) | |
| 187 * @param maxY Exclusive maximum y-coordinate. SkIRect's fTop and fBottom will
be | |
| 188 * in the range [0, maxY) | |
| 189 * @return SkIRect Non-empty, non-degenerate rectangle. | |
| 190 */ | |
| 191 static SkIRect generate_random_rect(SkRandom* rand, int32_t maxX, int32_t maxY)
{ | |
| 192 SkASSERT(maxX > 1 && maxY > 1); | |
| 193 int32_t left = rand->nextULessThan(maxX); | |
| 194 int32_t right = rand->nextULessThan(maxX); | |
| 195 int32_t top = rand->nextULessThan(maxY); | |
| 196 int32_t bottom = rand->nextULessThan(maxY); | |
| 197 SkIRect rect = SkIRect::MakeLTRB(left, top, right, bottom); | |
| 198 rect.sort(); | |
| 199 // Make sure rect is not empty. | |
| 200 if (rect.fLeft == rect.fRight) { | |
| 201 if (rect.fLeft > 0) { | |
| 202 rect.fLeft--; | |
| 203 } else { | |
| 204 rect.fRight++; | |
| 205 // This branch is only taken if 0 == rect.fRight, and | |
| 206 // maxX must be at least 2, so it must still be in | |
| 207 // range. | |
| 208 SkASSERT(rect.fRight < maxX); | |
| 209 } | |
| 210 } | |
| 211 if (rect.fTop == rect.fBottom) { | |
| 212 if (rect.fTop > 0) { | |
| 213 rect.fTop--; | |
| 214 } else { | |
| 215 rect.fBottom++; | |
| 216 // Again, this must be in range. | |
| 217 SkASSERT(rect.fBottom < maxY); | |
| 218 } | |
| 219 } | |
| 220 return rect; | |
| 221 } | |
| 222 | |
| 223 /** | |
| 224 * Return a string which includes the name of the file and the preferred config
, | |
| 225 * as specified by "--config". The resulting string will match the pattern of | |
| 226 * gm_json.py's IMAGE_FILENAME_PATTERN: "filename_config.png" | |
| 227 */ | |
| 228 static SkString create_json_key(const char* filename) { | |
| 229 SkASSERT(FLAGS_config.count() == 1); | |
| 230 return SkStringPrintf("%s_%s.png", filename, FLAGS_config[0]); | |
| 231 } | |
| 232 | |
| 233 // Stored expectations to be written to a file if createExpectationsPath is spec
ified. | |
| 234 static Json::Value gExpectationsToWrite; | |
| 235 | |
| 236 /** | |
| 237 * If expectations are to be recorded, record the bitmap expectations into the
global | |
| 238 * expectations array. | |
| 239 * As is the case with reading expectations, the key used will combine the file
name | |
| 240 * parameter with the preferred config, as specified by "--config", matching th
e | |
| 241 * pattern of gm_json.py's IMAGE_FILENAME_PATTERN: "filename_config.png" | |
| 242 */ | |
| 243 static void write_expectations(const skiagm::BitmapAndDigest& bitmapAndDigest, | |
| 244 const char* filename) { | |
| 245 const SkString name_config = create_json_key(filename); | |
| 246 if (!FLAGS_createExpectationsPath.isEmpty()) { | |
| 247 // Creates an Expectations object, and add it to the list to write. | |
| 248 skiagm::Expectations expectation(bitmapAndDigest); | |
| 249 Json::Value value = expectation.asJsonValue(); | |
| 250 gExpectationsToWrite[name_config.c_str()] = value; | |
| 251 } | |
| 252 } | |
| 253 | |
| 254 /** | |
| 255 * If --readExpectationsPath is set, compare this bitmap to the json expectatio
ns | |
| 256 * provided. | |
| 257 * | |
| 258 * @param digest GmResultDigest, computed from the decoded bitmap, to compare t
o | |
| 259 * the existing expectation. | |
| 260 * @param filename String used to find the expected value. Will be combined wit
h the | |
| 261 * preferred config, as specified by "--config", to match the pattern of | |
| 262 * gm_json.py's IMAGE_FILENAME_PATTERN: "filename_config.png". The resul
ting | |
| 263 * key will be used to find the proper expectations. | |
| 264 * @param failureArray Array to add a failure message to on failure. | |
| 265 * @param missingArray Array to add failure message to when missing image | |
| 266 * expectation. | |
| 267 * @param ignoreArray Array to add failure message to when the image does not m
atch | |
| 268 * the expectation, but this is a failure we can ignore. | |
| 269 * @return bool True in any of these cases: | |
| 270 * - the bitmap matches the expectation. | |
| 271 * False in any of these cases: | |
| 272 * - there is no expectations file. | |
| 273 * - there is an expectations file, but no expectation for this
bitmap. | |
| 274 * - there is an expectation for this bitmap, but it did not ma
tch. | |
| 275 * - expectation could not be computed from the bitmap. | |
| 276 */ | |
| 277 static bool compare_to_expectations_if_necessary(const skiagm::GmResultDigest& d
igest, | |
| 278 const char* filename, | |
| 279 SkTArray<SkString, false>* fail
ureArray, | |
| 280 SkTArray<SkString, false>* miss
ingArray, | |
| 281 SkTArray<SkString, false>* igno
reArray) { | |
| 282 // For both writing and reading, the key for this entry will include the nam
e | |
| 283 // of the file and the pref config, matching the pattern of gm_json.py's | |
| 284 // IMAGE_FILENAME_PATTERN: "name_config.png" | |
| 285 const SkString name_config = create_json_key(filename); | |
| 286 | |
| 287 if (!digest.isValid()) { | |
| 288 if (failureArray != NULL) { | |
| 289 failureArray->push_back().printf("decoded %s, but could not create a
GmResultDigest.", | |
| 290 filename); | |
| 291 } | |
| 292 return false; | |
| 293 } | |
| 294 | |
| 295 if (NULL == gJsonExpectations.get()) { | |
| 296 return false; | |
| 297 } | |
| 298 | |
| 299 skiagm::Expectations jsExpectation = gJsonExpectations->get(name_config.c_st
r()); | |
| 300 if (jsExpectation.empty()) { | |
| 301 if (missingArray != NULL) { | |
| 302 missingArray->push_back().printf("decoded %s, but could not find exp
ectation.", | |
| 303 filename); | |
| 304 } | |
| 305 return false; | |
| 306 } | |
| 307 | |
| 308 if (jsExpectation.match(digest)) { | |
| 309 return true; | |
| 310 } | |
| 311 | |
| 312 if (jsExpectation.ignoreFailure()) { | |
| 313 ignoreArray->push_back().printf("%s does not match expectation, but this
is known.", | |
| 314 filename); | |
| 315 } else if (failureArray != NULL) { | |
| 316 failureArray->push_back().printf("decoded %s, but the result does not ma
tch " | |
| 317 "expectations.", | |
| 318 filename); | |
| 319 } | |
| 320 return false; | |
| 321 } | |
| 322 | |
| 323 /** | |
| 324 * Helper function to write a bitmap subset to a file. Only called if subsets w
ere created | |
| 325 * and a writePath was provided. Behaves differently depending on | |
| 326 * FLAGS_writeChecksumBasedFilenames. If true: | |
| 327 * Writes the image to a PNG file named according to the digest hash, as de
scribed in | |
| 328 * write_bitmap. | |
| 329 * If false: | |
| 330 * Creates a subdirectory called 'subsets' and writes a PNG to that directo
ry. Also | |
| 331 * creates a subdirectory called 'extracted' and writes a bitmap created us
ing | |
| 332 * extractSubset to a PNG in that directory. Both files will represent the
same | |
| 333 * subrectangle and have the same name for convenient comparison. In this c
ase, the | |
| 334 * digest is ignored. | |
| 335 * | |
| 336 * @param writePath Parent directory to hold the folders for the PNG files to w
rite. Must | |
| 337 * not be NULL. | |
| 338 * @param subsetName Basename of the original file, with the dimensions of the
subset tacked | |
| 339 * on. Used to name the new file/folder. | |
| 340 * @param bitmapAndDigestFromDecodeSubset SkBitmap (with digest) created by | |
| 341 * SkImageDecoder::DecodeSubset, using rect as the area to decode. | |
| 342 * @param rect Rectangle of the area decoded into bitmapFromDecodeSubset. Used
to call | |
| 343 * extractSubset on originalBitmap to create a bitmap with the same dimensi
ons/pixels as | |
| 344 * bitmapFromDecodeSubset (assuming decodeSubset worked properly). | |
| 345 * @param originalBitmap SkBitmap decoded from the same stream as bitmapFromDec
odeSubset, | |
| 346 * using SkImageDecoder::decode to get the entire image. Used to create a P
NG file for | |
| 347 * comparison to the PNG created by bitmapAndDigestFromDecodeSubset's bitma
p. | |
| 348 * @return bool Whether the function succeeded at drawing the decoded subset an
d the extracted | |
| 349 * subset to files. | |
| 350 */ | |
| 351 static bool write_subset(const char* writePath, const SkString& subsetName, | |
| 352 const skiagm::BitmapAndDigest bitmapAndDigestFromDecod
eSubset, | |
| 353 SkIRect rect, const SkBitmap& originalBitmap) { | |
| 354 // All parameters must be valid. | |
| 355 SkASSERT(writePath != NULL); | |
| 356 | |
| 357 SkString subsetPath; | |
| 358 if (FLAGS_writeChecksumBasedFilenames) { | |
| 359 subsetPath.set(writePath); | |
| 360 } else { | |
| 361 // Create a subdirectory to hold the results of decodeSubset. | |
| 362 subsetPath = SkOSPath::Join(writePath, "subsets"); | |
| 363 if (!sk_mkdir(subsetPath.c_str())) { | |
| 364 gFailedSubsetDecodes.push_back().printf("Successfully decoded subset
%s, but " | |
| 365 "failed to create a director
y to write to.", | |
| 366 subsetName.c_str()); | |
| 367 return false; | |
| 368 } | |
| 369 } | |
| 370 SkAssertResult(write_bitmap(subsetPath.c_str(), subsetName.c_str(), | |
| 371 bitmapAndDigestFromDecodeSubset)); | |
| 372 gSuccessfulSubsetDecodes.push_back().printf("\twrote %s", subsetName.c_str()
); | |
| 373 | |
| 374 if (!FLAGS_writeChecksumBasedFilenames) { | |
| 375 // FIXME: The goal of extracting the subset is for visual comparison/usi
ng skdiff/skpdiff. | |
| 376 // Currently disabling for writeChecksumBasedFilenames since it will be
trickier to | |
| 377 // determine which files to compare. | |
| 378 | |
| 379 // Also use extractSubset from the original for visual comparison. | |
| 380 // Write the result to a file in a separate subdirectory. | |
| 381 SkBitmap extractedSubset; | |
| 382 if (!originalBitmap.extractSubset(&extractedSubset, rect)) { | |
| 383 gFailedSubsetDecodes.push_back().printf("Successfully decoded subset
%s, but failed " | |
| 384 "to extract a similar subset
for comparison.", | |
| 385 subsetName.c_str()); | |
| 386 return false; | |
| 387 } | |
| 388 | |
| 389 SkString dirExtracted = SkOSPath::Join(writePath, "extracted"); | |
| 390 if (!sk_mkdir(dirExtracted.c_str())) { | |
| 391 gFailedSubsetDecodes.push_back().printf("Successfully decoded subset
%s, but failed " | |
| 392 "to create a directory for e
xtractSubset " | |
| 393 "comparison.", | |
| 394 subsetName.c_str()); | |
| 395 return false; | |
| 396 } | |
| 397 | |
| 398 skiagm::BitmapAndDigest bitmapAndDigestFromExtractSubset(extractedSubset
); | |
| 399 SkAssertResult(write_bitmap(dirExtracted.c_str(), subsetName.c_str(), | |
| 400 bitmapAndDigestFromExtractSubset)); | |
| 401 } | |
| 402 return true; | |
| 403 } | |
| 404 | |
| 405 // FIXME: This test could be run on windows/mac once we remove their dependence
on | |
| 406 // getLength. See https://code.google.com/p/skia/issues/detail?id=1570 | |
| 407 #if defined(SK_BUILD_FOR_ANDROID) || defined(SK_BUILD_FOR_UNIX) | |
| 408 | |
| 409 /** | |
| 410 * Dummy class for testing to ensure that a stream without a length decodes the
same | |
| 411 * as a stream with a length. | |
| 412 */ | |
| 413 class FILEStreamWithoutLength : public SkFILEStream { | |
| 414 public: | |
| 415 FILEStreamWithoutLength(const char path[]) | |
| 416 : INHERITED(path) {} | |
| 417 | |
| 418 virtual bool hasLength() const SK_OVERRIDE { | |
| 419 return false; | |
| 420 } | |
| 421 | |
| 422 private: | |
| 423 typedef SkFILEStream INHERITED; | |
| 424 }; | |
| 425 | |
| 426 /** | |
| 427 * Test that decoding a stream which reports to not have a length still results
in the | |
| 428 * same image as if it did report to have a length. Assumes that codec was used
to | |
| 429 * successfully decode the file using SkFILEStream. | |
| 430 * @param srcPath The path to the file, for recreating the length-less stream. | |
| 431 * @param codec The SkImageDecoder originally used to decode srcPath, which wil
l be used | |
| 432 * again to decode the length-less stream. | |
| 433 * @param digest GmResultDigest computed from decoding the stream the first tim
e. | |
| 434 * Decoding the length-less stream is expected to result in a matching dige
st. | |
| 435 */ | |
| 436 static void test_stream_without_length(const char srcPath[], SkImageDecoder* cod
ec, | |
| 437 const skiagm::GmResultDigest& digest) { | |
| 438 if (!digest.isValid()) { | |
| 439 // An error was already reported. | |
| 440 return; | |
| 441 } | |
| 442 SkASSERT(srcPath); | |
| 443 SkASSERT(codec); | |
| 444 FILEStreamWithoutLength stream(srcPath); | |
| 445 // This will only be called after a successful decode. Creating a stream fro
m the same | |
| 446 // path should never fail. | |
| 447 SkASSERT(stream.isValid()); | |
| 448 SkBitmap bm; | |
| 449 if (!codec->decode(&stream, &bm, gPrefColorType, SkImageDecoder::kDecodePixe
ls_Mode)) { | |
| 450 gDecodeFailures.push_back().appendf("Without using getLength, %s failed
to decode\n", | |
| 451 srcPath); | |
| 452 return; | |
| 453 } | |
| 454 skiagm::GmResultDigest lengthLessDigest(bm); | |
| 455 if (!lengthLessDigest.isValid()) { | |
| 456 gDecodeFailures.push_back().appendf("Without using getLength, %s failed
to build " | |
| 457 "a digest\n", srcPath); | |
| 458 return; | |
| 459 } | |
| 460 if (!lengthLessDigest.equals(digest)) { | |
| 461 gDecodeFailures.push_back().appendf("Without using getLength, %s did not
match digest " | |
| 462 "that uses getLength\n", srcPath); | |
| 463 } | |
| 464 } | |
| 465 #endif // defined(SK_BUILD_FOR_ANDROID) || defined(SK_BUILD_FOR_UNIX) | |
| 466 | |
| 467 /** | |
| 468 * Replaces all instances of oldChar with newChar in str. | |
| 469 * | |
| 470 * TODO: This function appears here and in picture_utils.[cpp|h] ; | |
| 471 * we should add the implementation to src/core/SkString.cpp, write tests for it
, | |
| 472 * and remove it from elsewhere. | |
| 473 */ | |
| 474 static void replace_char(SkString* str, const char oldChar, const char newChar)
{ | |
| 475 if (NULL == str) { | |
| 476 return; | |
| 477 } | |
| 478 for (size_t i = 0; i < str->size(); ++i) { | |
| 479 if (oldChar == str->operator[](i)) { | |
| 480 str->operator[](i) = newChar; | |
| 481 } | |
| 482 } | |
| 483 } | |
| 484 | |
| 485 static void decodeFileAndWrite(const char srcPath[], const SkString* writePath)
{ | |
| 486 SkBitmap bitmap; | |
| 487 SkFILEStream stream(srcPath); | |
| 488 if (!stream.isValid()) { | |
| 489 gInvalidStreams.push_back().set(srcPath); | |
| 490 return; | |
| 491 } | |
| 492 | |
| 493 SkImageDecoder* codec = SkImageDecoder::Factory(&stream); | |
| 494 if (NULL == codec) { | |
| 495 gMissingCodecs.push_back().set(srcPath); | |
| 496 return; | |
| 497 } | |
| 498 | |
| 499 SkAutoTDelete<SkImageDecoder> ad(codec); | |
| 500 | |
| 501 codec->setSkipWritingZeroes(FLAGS_skip); | |
| 502 codec->setSampleSize(FLAGS_sampleSize); | |
| 503 codec->setRequireUnpremultipliedColors(FLAGS_unpremul); | |
| 504 stream.rewind(); | |
| 505 | |
| 506 // Create a string representing just the filename itself, for use in json ex
pectations. | |
| 507 SkString basename = SkOSPath::Basename(srcPath); | |
| 508 // Replace '_' with '-', so that the names can fit gm_json.py's IMAGE_FILENA
ME_PATTERN | |
| 509 replace_char(&basename, '_', '-'); | |
| 510 // Replace '.' with '-', so the output filename can still retain the origina
l file extension, | |
| 511 // but still end up with only one '.', which denotes the actual extension of
the final file. | |
| 512 replace_char(&basename, '.', '-'); | |
| 513 const char* filename = basename.c_str(); | |
| 514 | |
| 515 if (!codec->decode(&stream, &bitmap, gPrefColorType, SkImageDecoder::kDecode
Pixels_Mode)) { | |
| 516 if (gJsonExpectations.get()) { | |
| 517 const SkString name_config = create_json_key(filename); | |
| 518 skiagm::Expectations jsExpectations = gJsonExpectations->get(name_co
nfig.c_str()); | |
| 519 if (jsExpectations.ignoreFailure()) { | |
| 520 // This is a known failure. | |
| 521 gKnownFailures.push_back().appendf( | |
| 522 "failed to decode %s, which is a known failure.", srcPath); | |
| 523 return; | |
| 524 } | |
| 525 if (jsExpectations.empty()) { | |
| 526 // This is a failure, but it is a new file. Mark it as missing,
with | |
| 527 // a note that it should be marked failing. | |
| 528 gMissingExpectations.push_back().appendf( | |
| 529 "new file %s (with no expectations) FAILED to decode.", srcP
ath); | |
| 530 return; | |
| 531 } | |
| 532 } | |
| 533 | |
| 534 // If there was a failure, and either there was no expectations file, or | |
| 535 // the expectations file listed a valid expectation, report the failure. | |
| 536 gDecodeFailures.push_back().set(srcPath); | |
| 537 return; | |
| 538 } | |
| 539 | |
| 540 // Test decoding just the bounds. The bounds should always match. | |
| 541 { | |
| 542 stream.rewind(); | |
| 543 SkBitmap dim; | |
| 544 if (!codec->decode(&stream, &dim, SkImageDecoder::kDecodeBounds_Mode)) { | |
| 545 SkString failure = SkStringPrintf("failed to decode bounds for %s",
srcPath); | |
| 546 gDecodeFailures.push_back() = failure; | |
| 547 } else { | |
| 548 // Now check that the bounds match: | |
| 549 if (dim.width() != bitmap.width() || dim.height() != bitmap.height()
) { | |
| 550 SkString failure = SkStringPrintf("bounds do not match for %s",
srcPath); | |
| 551 gDecodeFailures.push_back() = failure; | |
| 552 } | |
| 553 } | |
| 554 } | |
| 555 | |
| 556 skiagm::BitmapAndDigest bitmapAndDigest(bitmap); | |
| 557 if (compare_to_expectations_if_necessary(bitmapAndDigest.fDigest, filename,
&gDecodeFailures, | |
| 558 &gMissingExpectations, &gKnownFailu
res)) { | |
| 559 gSuccessfulDecodes.push_back().printf("%s [%d %d]", srcPath, bitmap.widt
h(), | |
| 560 bitmap.height()); | |
| 561 } else if (!FLAGS_mismatchPath.isEmpty()) { | |
| 562 if (write_bitmap(FLAGS_mismatchPath[0], filename, bitmapAndDigest)) { | |
| 563 gSuccessfulDecodes.push_back().appendf("\twrote %s", filename); | |
| 564 } else { | |
| 565 gEncodeFailures.push_back().set(filename); | |
| 566 } | |
| 567 } | |
| 568 | |
| 569 // FIXME: This test could be run on windows/mac once we remove their dependence
on | |
| 570 // getLength. See https://code.google.com/p/skia/issues/detail?id=1570 | |
| 571 #if defined(SK_BUILD_FOR_ANDROID) || defined(SK_BUILD_FOR_UNIX) | |
| 572 test_stream_without_length(srcPath, codec, bitmapAndDigest.fDigest); | |
| 573 #endif | |
| 574 | |
| 575 if (writePath != NULL) { | |
| 576 if (write_bitmap(writePath->c_str(), filename, bitmapAndDigest)) { | |
| 577 gSuccessfulDecodes.push_back().appendf("\twrote %s", filename); | |
| 578 } else { | |
| 579 gEncodeFailures.push_back().set(filename); | |
| 580 } | |
| 581 } | |
| 582 | |
| 583 write_expectations(bitmapAndDigest, filename); | |
| 584 | |
| 585 if (FLAGS_testSubsetDecoding) { | |
| 586 SkDEBUGCODE(bool couldRewind =) stream.rewind(); | |
| 587 SkASSERT(couldRewind); | |
| 588 int width, height; | |
| 589 // Build the tile index for decoding subsets. If the image is 1x1, skip
subset | |
| 590 // decoding since there are no smaller subsets. | |
| 591 if (codec->buildTileIndex(&stream, &width, &height) && width > 1 && heig
ht > 1) { | |
| 592 SkASSERT((bitmap.width() == width && bitmap.height() == height) | |
| 593 || FLAGS_sampleSize != 1); | |
| 594 // Call decodeSubset multiple times: | |
| 595 SkRandom rand(0); | |
| 596 for (int i = 0; i < 5; i++) { | |
| 597 SkBitmap bitmapFromDecodeSubset; | |
| 598 // FIXME: Come up with a more representative set of rectangles. | |
| 599 SkIRect rect = generate_random_rect(&rand, width, height); | |
| 600 SkString subsetDim = SkStringPrintf("%d_%d_%d_%d", rect.fLeft, r
ect.fTop, | |
| 601 rect.fRight, rect.fBottom); | |
| 602 if (codec->decodeSubset(&bitmapFromDecodeSubset, rect, gPrefColo
rType)) { | |
| 603 SkString subsetName = SkStringPrintf("%s-%s", filename, subs
etDim.c_str()); | |
| 604 skiagm::BitmapAndDigest subsetBitmapAndDigest(bitmapFromDeco
deSubset); | |
| 605 if (compare_to_expectations_if_necessary(subsetBitmapAndDige
st.fDigest, | |
| 606 subsetName.c_str(), | |
| 607 &gFailedSubsetDecod
es, | |
| 608 &gMissingSubsetExpe
ctations, | |
| 609 &gKnownSubsetFailur
es)) { | |
| 610 gSuccessfulSubsetDecodes.push_back().printf("Decoded sub
set %s from %s", | |
| 611 subsetDim.c_str(),
srcPath); | |
| 612 } else if (!FLAGS_mismatchPath.isEmpty()) { | |
| 613 write_subset(FLAGS_mismatchPath[0], subsetName, | |
| 614 subsetBitmapAndDigest, rect, bitmap); | |
| 615 } | |
| 616 | |
| 617 write_expectations(subsetBitmapAndDigest, subsetName.c_str()
); | |
| 618 | |
| 619 if (writePath != NULL) { | |
| 620 write_subset(writePath->c_str(), subsetName, | |
| 621 subsetBitmapAndDigest, rect, bitmap); | |
| 622 } | |
| 623 } else { | |
| 624 gFailedSubsetDecodes.push_back().printf("Failed to decode re
gion %s from %s", | |
| 625 subsetDim.c_str(), s
rcPath); | |
| 626 } | |
| 627 } | |
| 628 } | |
| 629 } | |
| 630 | |
| 631 // Do not attempt to re-encode A8, since our image encoders do not support e
ncoding to A8. | |
| 632 if (FLAGS_reencode && bitmap.colorType() != kAlpha_8_SkColorType) { | |
| 633 // Encode to the format the file was originally in, or PNG if the encode
r for the same | |
| 634 // format is unavailable. | |
| 635 SkImageDecoder::Format format = codec->getFormat(); | |
| 636 if (SkImageDecoder::kUnknown_Format == format) { | |
| 637 if (stream.rewind()) { | |
| 638 format = SkImageDecoder::GetStreamFormat(&stream); | |
| 639 } | |
| 640 if (SkImageDecoder::kUnknown_Format == format) { | |
| 641 const char* dot = strrchr(srcPath, '.'); | |
| 642 if (dot) { | |
| 643 format = guess_format_from_suffix(dot); | |
| 644 } | |
| 645 if (SkImageDecoder::kUnknown_Format == format) { | |
| 646 SkDebugf("Could not determine type for '%s'\n", srcPath); | |
| 647 format = SkImageDecoder::kPNG_Format; | |
| 648 } | |
| 649 | |
| 650 } | |
| 651 } else { | |
| 652 SkASSERT(!stream.rewind() || SkImageDecoder::GetStreamFormat(&stream
) == format); | |
| 653 } | |
| 654 SkImageEncoder::Type type = format_to_type(format); | |
| 655 // format should never be kUnknown_Format, so type should never be kUnkn
own_Type. | |
| 656 SkASSERT(type != SkImageEncoder::kUnknown_Type); | |
| 657 | |
| 658 SkImageEncoder* encoder = SkImageEncoder::Create(type); | |
| 659 if (NULL == encoder) { | |
| 660 type = SkImageEncoder::kPNG_Type; | |
| 661 encoder = SkImageEncoder::Create(type); | |
| 662 SkASSERT(encoder); | |
| 663 } | |
| 664 SkAutoTDelete<SkImageEncoder> ade(encoder); | |
| 665 // Encode to a stream. | |
| 666 SkDynamicMemoryWStream wStream; | |
| 667 if (!encoder->encodeStream(&wStream, bitmap, 100)) { | |
| 668 gEncodeFailures.push_back().printf("Failed to reencode %s to type '%
s'", srcPath, | |
| 669 suffix_for_type(type)); | |
| 670 return; | |
| 671 } | |
| 672 | |
| 673 SkAutoTUnref<SkData> data(wStream.copyToData()); | |
| 674 if (writePath != NULL && type != SkImageEncoder::kPNG_Type) { | |
| 675 // Write the encoded data to a file. Do not write to PNG, which was
already written. | |
| 676 SkString outPath; | |
| 677 make_outname(&outPath, writePath->c_str(), filename, suffix_for_type
(type)); | |
| 678 SkFILEWStream file(outPath.c_str()); | |
| 679 if(file.write(data->data(), data->size())) { | |
| 680 gSuccessfulDecodes.push_back().appendf("\twrote %s", outPath.c_s
tr()); | |
| 681 } else { | |
| 682 gEncodeFailures.push_back().printf("Failed to write %s", outPath
.c_str()); | |
| 683 } | |
| 684 } | |
| 685 // Ensure that the reencoded data can still be decoded. | |
| 686 SkMemoryStream memStream(data); | |
| 687 SkBitmap redecodedBitmap; | |
| 688 SkImageDecoder::Format formatOnSecondDecode; | |
| 689 if (SkImageDecoder::DecodeStream(&memStream, &redecodedBitmap, gPrefColo
rType, | |
| 690 SkImageDecoder::kDecodePixels_Mode, | |
| 691 &formatOnSecondDecode)) { | |
| 692 SkASSERT(format_to_type(formatOnSecondDecode) == type); | |
| 693 } else { | |
| 694 gDecodeFailures.push_back().printf("Failed to redecode %s after reen
coding to '%s'", | |
| 695 srcPath, suffix_for_type(type)); | |
| 696 } | |
| 697 } | |
| 698 } | |
| 699 | |
| 700 /////////////////////////////////////////////////////////////////////////////// | |
| 701 | |
| 702 // If strings is not empty, print title, followed by each string on its own line
starting | |
| 703 // with a tab. | |
| 704 // @return bool True if strings had at least one entry. | |
| 705 static bool print_strings(const char* title, const SkTArray<SkString, false>& st
rings) { | |
| 706 if (strings.count() > 0) { | |
| 707 SkDebugf("%s:\n", title); | |
| 708 for (int i = 0; i < strings.count(); i++) { | |
| 709 SkDebugf("\t%s\n", strings[i].c_str()); | |
| 710 } | |
| 711 SkDebugf("\n"); | |
| 712 return true; | |
| 713 } | |
| 714 return false; | |
| 715 } | |
| 716 | |
| 717 /** | |
| 718 * If directory is non null and does not end with a path separator, append one. | |
| 719 * @param directory SkString representing the path to a directory. If the last
character is not a | |
| 720 * path separator (specific to the current OS), append one. | |
| 721 */ | |
| 722 static void append_path_separator_if_necessary(SkString* directory) { | |
| 723 if (directory != NULL && directory->c_str()[directory->size() - 1] != SkPATH
_SEPARATOR) { | |
| 724 directory->appendf("%c", SkPATH_SEPARATOR); | |
| 725 } | |
| 726 } | |
| 727 | |
| 728 /** | |
| 729 * Return true if the filename represents an image. | |
| 730 */ | |
| 731 static bool is_image_file(const char* filename) { | |
| 732 const char* gImageExtensions[] = { | |
| 733 ".png", ".PNG", ".jpg", ".JPG", ".jpeg", ".JPEG", ".bmp", ".BMP", | |
| 734 ".webp", ".WEBP", ".ico", ".ICO", ".wbmp", ".WBMP", ".gif", ".GIF" | |
| 735 }; | |
| 736 for (size_t i = 0; i < SK_ARRAY_COUNT(gImageExtensions); ++i) { | |
| 737 if (SkStrEndsWith(filename, gImageExtensions[i])) { | |
| 738 return true; | |
| 739 } | |
| 740 } | |
| 741 return false; | |
| 742 } | |
| 743 | |
| 744 int tool_main(int argc, char** argv); | |
| 745 int tool_main(int argc, char** argv) { | |
| 746 SkCommandLineFlags::SetUsage("Decode files, and optionally write the results
to files."); | |
| 747 SkCommandLineFlags::Parse(argc, argv); | |
| 748 | |
| 749 if (FLAGS_readPath.count() < 1) { | |
| 750 SkDebugf("Folder(s) or image(s) to decode are required.\n"); | |
| 751 return -1; | |
| 752 } | |
| 753 | |
| 754 | |
| 755 SkAutoGraphics ag; | |
| 756 | |
| 757 if (!FLAGS_readExpectationsPath.isEmpty() && sk_exists(FLAGS_readExpectation
sPath[0])) { | |
| 758 gJsonExpectations.reset(SkNEW_ARGS(skiagm::JsonExpectationsSource, | |
| 759 (FLAGS_readExpectationsPath[0]))); | |
| 760 } | |
| 761 | |
| 762 SkString outDir; | |
| 763 SkString* outDirPtr; | |
| 764 | |
| 765 if (FLAGS_writePath.count() == 1) { | |
| 766 outDir.set(FLAGS_writePath[0]); | |
| 767 append_path_separator_if_necessary(&outDir); | |
| 768 outDirPtr = &outDir; | |
| 769 } else { | |
| 770 outDirPtr = NULL; | |
| 771 } | |
| 772 | |
| 773 if (FLAGS_config.count() == 1) { | |
| 774 // Only consider the first config specified on the command line. | |
| 775 const char* config = FLAGS_config[0]; | |
| 776 if (0 == strcmp(config, "8888")) { | |
| 777 gPrefColorType = kN32_SkColorType; | |
| 778 } else if (0 == strcmp(config, "565")) { | |
| 779 gPrefColorType = kRGB_565_SkColorType; | |
| 780 } else if (0 == strcmp(config, "A8")) { | |
| 781 gPrefColorType = kAlpha_8_SkColorType; | |
| 782 } else if (0 != strcmp(config, "None")) { | |
| 783 SkDebugf("Invalid preferred config\n"); | |
| 784 return -1; | |
| 785 } | |
| 786 } | |
| 787 | |
| 788 for (int i = 0; i < FLAGS_readPath.count(); i++) { | |
| 789 const char* readPath = FLAGS_readPath[i]; | |
| 790 if (strlen(readPath) < 1) { | |
| 791 break; | |
| 792 } | |
| 793 if (sk_isdir(readPath)) { | |
| 794 const char* dir = readPath; | |
| 795 SkOSFile::Iter iter(dir); | |
| 796 SkString filename; | |
| 797 while (iter.next(&filename)) { | |
| 798 if (!is_image_file(filename.c_str())) { | |
| 799 continue; | |
| 800 } | |
| 801 SkString fullname = SkOSPath::Join(dir, filename.c_str()); | |
| 802 decodeFileAndWrite(fullname.c_str(), outDirPtr); | |
| 803 } | |
| 804 } else if (sk_exists(readPath) && is_image_file(readPath)) { | |
| 805 decodeFileAndWrite(readPath, outDirPtr); | |
| 806 } | |
| 807 } | |
| 808 | |
| 809 if (!FLAGS_createExpectationsPath.isEmpty()) { | |
| 810 // Use an empty value for everything besides expectations, since the rea
der only cares | |
| 811 // about the expectations. | |
| 812 Json::Value nullValue; | |
| 813 Json::Value root = skiagm::CreateJsonTree(gExpectationsToWrite, nullValu
e, nullValue, | |
| 814 nullValue, nullValue); | |
| 815 std::string jsonStdString = root.toStyledString(); | |
| 816 SkFILEWStream stream(FLAGS_createExpectationsPath[0]); | |
| 817 stream.write(jsonStdString.c_str(), jsonStdString.length()); | |
| 818 } | |
| 819 // Add some space, since codecs may print warnings without newline. | |
| 820 SkDebugf("\n\n"); | |
| 821 | |
| 822 bool failed = print_strings("Invalid files", gInvalidStreams); | |
| 823 failed |= print_strings("Missing codec", gMissingCodecs); | |
| 824 failed |= print_strings("Failed to decode", gDecodeFailures); | |
| 825 failed |= print_strings("Failed to encode", gEncodeFailures); | |
| 826 print_strings("Decoded", gSuccessfulDecodes); | |
| 827 print_strings("Missing expectations", gMissingExpectations); | |
| 828 | |
| 829 if (FLAGS_testSubsetDecoding) { | |
| 830 failed |= print_strings("Failed subset decodes", gFailedSubsetDecodes); | |
| 831 print_strings("Decoded subsets", gSuccessfulSubsetDecodes); | |
| 832 print_strings("Missing subset expectations", gMissingSubsetExpectations)
; | |
| 833 print_strings("Known subset failures", gKnownSubsetFailures); | |
| 834 } | |
| 835 | |
| 836 print_strings("Known failures", gKnownFailures); | |
| 837 | |
| 838 return failed ? -1 : 0; | |
| 839 } | |
| 840 | |
| 841 #if !defined SK_BUILD_FOR_IOS | |
| 842 int main(int argc, char * const argv[]) { | |
| 843 return tool_main(argc, (char**) argv); | |
| 844 } | |
| 845 #endif | |
| OLD | NEW |