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 |