blob: 5b8f09548535069b5f8c347cb12c1b24992bbd82 [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001/*
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 */
scroggo@google.comb41ff952013-04-11 15:53:35 +00007
scroggo@google.com6843bdb2013-05-08 19:14:23 +00008#include "gm_expectations.h"
reed@android.comaf459792009-04-24 19:52:53 +00009#include "SkBitmap.h"
scroggo@google.com39edf4c2013-04-25 17:33:51 +000010#include "SkColorPriv.h"
scroggo@google.comb41ff952013-04-11 15:53:35 +000011#include "SkCommandLineFlags.h"
scroggo@google.com39edf4c2013-04-25 17:33:51 +000012#include "SkData.h"
scroggo@google.com7def5e12013-05-31 14:00:10 +000013#include "SkForceLinking.h"
reed@android.comaf459792009-04-24 19:52:53 +000014#include "SkGraphics.h"
15#include "SkImageDecoder.h"
16#include "SkImageEncoder.h"
scroggo@google.comb41ff952013-04-11 15:53:35 +000017#include "SkOSFile.h"
scroggo@google.com7e6fcee2013-05-03 20:14:28 +000018#include "SkRandom.h"
reed@android.comaf459792009-04-24 19:52:53 +000019#include "SkStream.h"
scroggo@google.comb41ff952013-04-11 15:53:35 +000020#include "SkTArray.h"
reed@android.comaf459792009-04-24 19:52:53 +000021#include "SkTemplates.h"
22
scroggo@google.com7def5e12013-05-31 14:00:10 +000023__SK_FORCE_IMAGE_DECODER_LINKING;
24
scroggo@google.com6d99de12013-08-06 18:56:53 +000025DEFINE_string(config, "None", "Preferred config to decode into. [None|8888|565|A8]");
scroggo@google.com6843bdb2013-05-08 19:14:23 +000026DEFINE_string(createExpectationsPath, "", "Path to write JSON expectations.");
scroggo@google.comcf5eb6a2013-06-07 12:43:15 +000027DEFINE_string(mismatchPath, "", "Folder to write mismatched images to.");
scroggo@google.comb41ff952013-04-11 15:53:35 +000028DEFINE_string2(readPath, r, "", "Folder(s) and files to decode images. Required.");
scroggo@google.com6843bdb2013-05-08 19:14:23 +000029DEFINE_string(readExpectationsPath, "", "Path to read JSON expectations from.");
scroggo@google.com39edf4c2013-04-25 17:33:51 +000030DEFINE_bool(reencode, true, "Reencode the images to test encoding.");
commit-bot@chromium.orgdac4a1d2013-10-08 19:40:18 +000031DEFINE_int32(sampleSize, 1, "Set the sampleSize for decoding.");
32DEFINE_bool(skip, false, "Skip writing zeroes.");
scroggo@google.com7e6fcee2013-05-03 20:14:28 +000033DEFINE_bool(testSubsetDecoding, true, "Test decoding subsets of images.");
scroggo@google.comcf5eb6a2013-06-07 12:43:15 +000034DEFINE_string2(writePath, w, "", "Write rendered images into this directory.");
scroggo@google.comb41ff952013-04-11 15:53:35 +000035
scroggo@google.com39edf4c2013-04-25 17:33:51 +000036struct Format {
37 SkImageEncoder::Type fType;
38 SkImageDecoder::Format fFormat;
39 const char* fSuffix;
40};
scroggo@google.comb41ff952013-04-11 15:53:35 +000041
scroggo@google.com39edf4c2013-04-25 17:33:51 +000042static const Format gFormats[] = {
43 { SkImageEncoder::kBMP_Type, SkImageDecoder::kBMP_Format, ".bmp" },
44 { SkImageEncoder::kGIF_Type, SkImageDecoder::kGIF_Format, ".gif" },
45 { SkImageEncoder::kICO_Type, SkImageDecoder::kICO_Format, ".ico" },
46 { SkImageEncoder::kJPEG_Type, SkImageDecoder::kJPEG_Format, ".jpg" },
47 { SkImageEncoder::kPNG_Type, SkImageDecoder::kPNG_Format, ".png" },
48 { SkImageEncoder::kWBMP_Type, SkImageDecoder::kWBMP_Format, ".wbmp" },
49 { SkImageEncoder::kWEBP_Type, SkImageDecoder::kWEBP_Format, ".webp" }
50};
51
52static SkImageEncoder::Type format_to_type(SkImageDecoder::Format format) {
53 for (size_t i = 0; i < SK_ARRAY_COUNT(gFormats); i++) {
54 if (gFormats[i].fFormat == format) {
55 return gFormats[i].fType;
56 }
reed@android.comaf459792009-04-24 19:52:53 +000057 }
scroggo@google.com39edf4c2013-04-25 17:33:51 +000058 return SkImageEncoder::kUnknown_Type;
reed@android.comaf459792009-04-24 19:52:53 +000059}
60
scroggo@google.com39edf4c2013-04-25 17:33:51 +000061static const char* suffix_for_type(SkImageEncoder::Type type) {
62 for (size_t i = 0; i < SK_ARRAY_COUNT(gFormats); i++) {
63 if (gFormats[i].fType == type) {
64 return gFormats[i].fSuffix;
65 }
66 }
67 return "";
68}
reed@android.comaf459792009-04-24 19:52:53 +000069
scroggo@google.com39edf4c2013-04-25 17:33:51 +000070static SkImageDecoder::Format guess_format_from_suffix(const char suffix[]) {
71 for (size_t i = 0; i < SK_ARRAY_COUNT(gFormats); i++) {
72 if (strcmp(suffix, gFormats[i].fSuffix) == 0) {
73 return gFormats[i].fFormat;
74 }
75 }
76 return SkImageDecoder::kUnknown_Format;
77}
78
79static void make_outname(SkString* dst, const char outDir[], const char src[],
80 const char suffix[]) {
scroggo@google.comccd7afb2013-05-28 16:45:07 +000081 SkString basename = SkOSPath::SkBasename(src);
82 dst->set(SkOSPath::SkPathJoin(outDir, basename.c_str()));
scroggo@google.com39edf4c2013-04-25 17:33:51 +000083 if (!dst->endsWith(suffix)) {
scroggo@google.comb41ff952013-04-11 15:53:35 +000084 const char* cstyleDst = dst->c_str();
85 const char* dot = strrchr(cstyleDst, '.');
86 if (dot != NULL) {
87 int32_t index = SkToS32(dot - cstyleDst);
88 dst->remove(index, dst->size() - index);
89 }
scroggo@google.com39edf4c2013-04-25 17:33:51 +000090 dst->append(suffix);
scroggo@google.comb41ff952013-04-11 15:53:35 +000091 }
92}
93
scroggo@google.com39edf4c2013-04-25 17:33:51 +000094// Store the names of the filenames to report later which ones failed, succeeded, and were
95// invalid.
scroggo@google.com36c5bdb2013-10-15 20:29:37 +000096// FIXME: Add more arrays, for more specific types of errors, and make the output simpler.
97// If each array holds one type of error, the output can change from:
98//
99// Failures:
100// <image> failed for such and such reason
101// <image> failed for some different reason
102//
103// to:
104//
105// Such and such failures:
106// <image>
107//
108// Different reason failures:
109// <image>
110//
scroggo@google.com39edf4c2013-04-25 17:33:51 +0000111static SkTArray<SkString, false> gInvalidStreams;
112static SkTArray<SkString, false> gMissingCodecs;
113static SkTArray<SkString, false> gDecodeFailures;
114static SkTArray<SkString, false> gEncodeFailures;
115static SkTArray<SkString, false> gSuccessfulDecodes;
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000116static SkTArray<SkString, false> gSuccessfulSubsetDecodes;
117static SkTArray<SkString, false> gFailedSubsetDecodes;
scroggo@google.come339eb02013-08-06 18:51:30 +0000118// Files/subsets that do not have expectations. Not reported as a failure of the test so
119// the bots will not turn red with each new image test.
120static SkTArray<SkString, false> gMissingExpectations;
121static SkTArray<SkString, false> gMissingSubsetExpectations;
scroggo@google.com36c5bdb2013-10-15 20:29:37 +0000122// For files that are expected to fail.
123static SkTArray<SkString, false> gKnownFailures;
124static SkTArray<SkString, false> gKnownSubsetFailures;
scroggo@google.com39edf4c2013-04-25 17:33:51 +0000125
scroggo@google.com6d99de12013-08-06 18:56:53 +0000126static SkBitmap::Config gPrefConfig(SkBitmap::kNo_Config);
127
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000128// Expections read from a file specified by readExpectationsPath. The expectations must have been
129// previously written using createExpectationsPath.
130SkAutoTUnref<skiagm::JsonExpectationsSource> gJsonExpectations;
131
scroggo@google.comcf5eb6a2013-06-07 12:43:15 +0000132static bool write_bitmap(const char outName[], const SkBitmap& bm) {
scroggo@google.comde09af32013-10-07 16:56:01 +0000133 if (SkImageEncoder::EncodeFile(outName, bm, SkImageEncoder::kPNG_Type, 100)) {
134 return true;
commit-bot@chromium.org546f70c2013-10-03 17:13:38 +0000135 }
scroggo@google.comde09af32013-10-07 16:56:01 +0000136
137 if (bm.config() == SkBitmap::kARGB_8888_Config) {
138 // First attempt at encoding failed, and the bitmap was already 8888. Making
139 // a copy is not going to help.
140 return false;
141 }
142
143 // Encoding failed. Copy to 8888 and try again.
144 SkBitmap bm8888;
145 if (!bm.copyTo(&bm8888, SkBitmap::kARGB_8888_Config)) {
146 return false;
147 }
148 return SkImageEncoder::EncodeFile(outName, bm8888, SkImageEncoder::kPNG_Type, 100);
scroggo@google.com39edf4c2013-04-25 17:33:51 +0000149}
150
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000151/**
152 * Return a random SkIRect inside the range specified.
153 * @param rand Random number generator.
154 * @param maxX Exclusive maximum x-coordinate. SkIRect's fLeft and fRight will be
155 * in the range [0, maxX)
156 * @param maxY Exclusive maximum y-coordinate. SkIRect's fTop and fBottom will be
157 * in the range [0, maxY)
158 * @return SkIRect Non-empty, non-degenerate rectangle.
159 */
160static SkIRect generate_random_rect(SkRandom* rand, int32_t maxX, int32_t maxY) {
161 SkASSERT(maxX > 1 && maxY > 1);
162 int32_t left = rand->nextULessThan(maxX);
163 int32_t right = rand->nextULessThan(maxX);
164 int32_t top = rand->nextULessThan(maxY);
165 int32_t bottom = rand->nextULessThan(maxY);
166 SkIRect rect = SkIRect::MakeLTRB(left, top, right, bottom);
167 rect.sort();
168 // Make sure rect is not empty.
169 if (rect.fLeft == rect.fRight) {
170 if (rect.fLeft > 0) {
171 rect.fLeft--;
172 } else {
173 rect.fRight++;
174 // This branch is only taken if 0 == rect.fRight, and
175 // maxX must be at least 2, so it must still be in
176 // range.
177 SkASSERT(rect.fRight < maxX);
178 }
179 }
180 if (rect.fTop == rect.fBottom) {
181 if (rect.fTop > 0) {
182 rect.fTop--;
183 } else {
184 rect.fBottom++;
185 // Again, this must be in range.
186 SkASSERT(rect.fBottom < maxY);
187 }
188 }
189 return rect;
190}
191
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000192// Stored expectations to be written to a file if createExpectationsPath is specified.
193static Json::Value gExpectationsToWrite;
194
195/**
epoger@google.comd4993ff2013-05-24 14:33:28 +0000196 * If expectations are to be recorded, record the bitmap expectations into global
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000197 * expectations array.
198 */
199static void write_expectations(const SkBitmap& bitmap, const char* filename) {
200 if (!FLAGS_createExpectationsPath.isEmpty()) {
201 // Creates an Expectations object, and add it to the list to write.
202 skiagm::Expectations expectation(bitmap);
203 Json::Value value = expectation.asJsonValue();
204 gExpectationsToWrite[filename] = value;
205 }
206}
207
208/**
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000209 * Compare against an expectation for this filename, if there is one.
scroggo@google.comcd7a73c2013-08-28 18:33:31 +0000210 * @param digest GmResultDigest, computed from the decoded bitmap, to compare to the
scroggo@google.com36c5bdb2013-10-15 20:29:37 +0000211 * expectation.
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000212 * @param filename String used to find the expected value.
scroggo@google.come339eb02013-08-06 18:51:30 +0000213 * @param failureArray Array to add a failure message to on failure.
scroggo@google.com36c5bdb2013-10-15 20:29:37 +0000214 * @param missingArray Array to add failure message to when missing image
215 * expectation.
216 * @param ignoreArray Array to add failure message to when the image does not match
217 * the expectation, but this is a failure we can ignore.
scroggo@google.com6ca30ca2013-05-14 17:30:17 +0000218 * @return bool True in any of these cases:
219 * - the bitmap matches the expectation.
scroggo@google.com6ca30ca2013-05-14 17:30:17 +0000220 * False in any of these cases:
scroggo@google.comcf5eb6a2013-06-07 12:43:15 +0000221 * - there is no expectations file.
scroggo@google.com6ca30ca2013-05-14 17:30:17 +0000222 * - there is an expectations file, but no expectation for this bitmap.
223 * - there is an expectation for this bitmap, but it did not match.
224 * - expectation could not be computed from the bitmap.
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000225 */
scroggo@google.comcd7a73c2013-08-28 18:33:31 +0000226static bool compare_to_expectations_if_necessary(const skiagm::GmResultDigest& digest,
227 const char* filename,
scroggo@google.come339eb02013-08-06 18:51:30 +0000228 SkTArray<SkString, false>* failureArray,
scroggo@google.com36c5bdb2013-10-15 20:29:37 +0000229 SkTArray<SkString, false>* missingArray,
230 SkTArray<SkString, false>* ignoreArray) {
scroggo@google.comcd7a73c2013-08-28 18:33:31 +0000231 if (!digest.isValid()) {
scroggo@google.comcf5eb6a2013-06-07 12:43:15 +0000232 if (failureArray != NULL) {
233 failureArray->push_back().printf("decoded %s, but could not create a GmResultDigest.",
234 filename);
235 }
236 return false;
237 }
238
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000239 if (NULL == gJsonExpectations.get()) {
scroggo@google.comcf5eb6a2013-06-07 12:43:15 +0000240 return false;
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000241 }
242
243 skiagm::Expectations jsExpectation = gJsonExpectations->get(filename);
244 if (jsExpectation.empty()) {
scroggo@google.come339eb02013-08-06 18:51:30 +0000245 if (missingArray != NULL) {
246 missingArray->push_back().printf("decoded %s, but could not find expectation.",
scroggo@google.com6ca30ca2013-05-14 17:30:17 +0000247 filename);
248 }
249 return false;
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000250 }
251
scroggo@google.comcd7a73c2013-08-28 18:33:31 +0000252 if (jsExpectation.match(digest)) {
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000253 return true;
254 }
255
scroggo@google.com36c5bdb2013-10-15 20:29:37 +0000256 if (jsExpectation.ignoreFailure()) {
257 ignoreArray->push_back().printf("%s does not match expectation, but this is known.",
258 filename);
259 } else if (failureArray != NULL) {
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000260 failureArray->push_back().printf("decoded %s, but the result does not match "
261 "expectations.",
262 filename);
263 }
264 return false;
265}
266
scroggo@google.com9da0e1d2013-05-15 18:14:36 +0000267/**
268 * Helper function to write a bitmap subset to a file. Only called if subsets were created
269 * and a writePath was provided. Creates a subdirectory called 'subsets' and writes a PNG to
270 * that directory. Also creates a subdirectory called 'extracted' and writes a bitmap created
271 * using extractSubset to a PNG in that directory. Both files will represent the same
272 * subrectangle and have the same name for comparison.
273 * @param writePath Parent directory to hold the folders for the PNG files to write. Must
274 * not be NULL.
275 * @param filename Basename of the original file. Used to name the new files. Must not be
276 * NULL.
277 * @param subsetDim String representing the dimensions of the subset. Used to name the new
278 * files. Must not be NULL.
279 * @param bitmapFromDecodeSubset Pointer to SkBitmap created by SkImageDecoder::DecodeSubset,
280 * using rect as the area to decode.
281 * @param rect Rectangle of the area decoded into bitmapFromDecodeSubset. Used to call
282 * extractSubset on originalBitmap to create a bitmap with the same dimensions/pixels as
283 * bitmapFromDecodeSubset (assuming decodeSubset worked properly).
284 * @param originalBitmap SkBitmap decoded from the same stream as bitmapFromDecodeSubset,
285 * using SkImageDecoder::decode to get the entire image. Used to create a PNG file for
286 * comparison to the PNG created by bitmapFromDecodeSubset.
287 * @return bool Whether the function succeeded at drawing the decoded subset and the extracted
288 * subset to files.
289 */
290static bool write_subset(const char* writePath, const char* filename, const char* subsetDim,
291 SkBitmap* bitmapFromDecodeSubset, SkIRect rect,
292 const SkBitmap& originalBitmap) {
293 // All parameters must be valid.
294 SkASSERT(writePath != NULL);
295 SkASSERT(filename != NULL);
296 SkASSERT(subsetDim != NULL);
297 SkASSERT(bitmapFromDecodeSubset != NULL);
298
299 // Create a subdirectory to hold the results of decodeSubset.
scroggo@google.comccd7afb2013-05-28 16:45:07 +0000300 SkString dir = SkOSPath::SkPathJoin(writePath, "subsets");
scroggo@google.com9da0e1d2013-05-15 18:14:36 +0000301 if (!sk_mkdir(dir.c_str())) {
302 gFailedSubsetDecodes.push_back().printf("Successfully decoded %s from %s, but failed to "
303 "create a directory to write to.", subsetDim,
304 filename);
305 return false;
306 }
307
308 // Write the subset to a file whose name includes the dimensions.
309 SkString suffix = SkStringPrintf("_%s.png", subsetDim);
310 SkString outPath;
311 make_outname(&outPath, dir.c_str(), filename, suffix.c_str());
scroggo@google.comcf5eb6a2013-06-07 12:43:15 +0000312 SkAssertResult(write_bitmap(outPath.c_str(), *bitmapFromDecodeSubset));
scroggo@google.com9da0e1d2013-05-15 18:14:36 +0000313 gSuccessfulSubsetDecodes.push_back().printf("\twrote %s", outPath.c_str());
314
315 // Also use extractSubset from the original for visual comparison.
316 // Write the result to a file in a separate subdirectory.
317 SkBitmap extractedSubset;
318 if (!originalBitmap.extractSubset(&extractedSubset, rect)) {
319 gFailedSubsetDecodes.push_back().printf("Successfully decoded %s from %s, but failed to "
320 "extract a similar subset for comparison.",
321 subsetDim, filename);
322 return false;
323 }
324
scroggo@google.comccd7afb2013-05-28 16:45:07 +0000325 SkString dirExtracted = SkOSPath::SkPathJoin(writePath, "extracted");
scroggo@google.com9da0e1d2013-05-15 18:14:36 +0000326 if (!sk_mkdir(dirExtracted.c_str())) {
327 gFailedSubsetDecodes.push_back().printf("Successfully decoded %s from %s, but failed to "
328 "create a directory for extractSubset comparison.",
329 subsetDim, filename);
330 return false;
331 }
332
333 make_outname(&outPath, dirExtracted.c_str(), filename, suffix.c_str());
scroggo@google.comcf5eb6a2013-06-07 12:43:15 +0000334 SkAssertResult(write_bitmap(outPath.c_str(), extractedSubset));
scroggo@google.com9da0e1d2013-05-15 18:14:36 +0000335 return true;
336}
337
scroggo@google.comcd7a73c2013-08-28 18:33:31 +0000338// FIXME: This test could be run on windows/mac once we remove their dependence on
339// getLength. See https://code.google.com/p/skia/issues/detail?id=1570
340#if defined(SK_BUILD_FOR_ANDROID) || defined(SK_BUILD_FOR_UNIX)
341
342/**
343 * Dummy class for testing to ensure that a stream without a length decodes the same
344 * as a stream with a length.
345 */
346class FILEStreamWithoutLength : public SkFILEStream {
347public:
348 FILEStreamWithoutLength(const char path[])
349 : INHERITED(path) {}
350
351 virtual bool hasLength() const SK_OVERRIDE {
352 return false;
353 }
354
355private:
356 typedef SkFILEStream INHERITED;
357};
358
359/**
360 * Test that decoding a stream which reports to not have a length still results in the
361 * same image as if it did report to have a length. Assumes that codec was used to
362 * successfully decode the file using SkFILEStream.
363 * @param srcPath The path to the file, for recreating the length-less stream.
364 * @param codec The SkImageDecoder originally used to decode srcPath, which will be used
365 * again to decode the length-less stream.
366 * @param digest GmResultDigest computed from decoding the stream the first time.
367 * Decoding the length-less stream is expected to result in a matching digest.
368 */
369static void test_stream_without_length(const char srcPath[], SkImageDecoder* codec,
370 const skiagm::GmResultDigest& digest) {
371 if (!digest.isValid()) {
372 // An error was already reported.
373 return;
374 }
375 SkASSERT(srcPath);
376 SkASSERT(codec);
377 FILEStreamWithoutLength stream(srcPath);
378 // This will only be called after a successful decode. Creating a stream from the same
379 // path should never fail.
380 SkASSERT(stream.isValid());
381 SkBitmap bm;
382 if (!codec->decode(&stream, &bm, gPrefConfig, SkImageDecoder::kDecodePixels_Mode)) {
383 gDecodeFailures.push_back().appendf("Without using getLength, %s failed to decode\n",
384 srcPath);
385 return;
386 }
387 skiagm::GmResultDigest lengthLessDigest(bm);
388 if (!lengthLessDigest.isValid()) {
389 gDecodeFailures.push_back().appendf("Without using getLength, %s failed to build "
390 "a digest\n", srcPath);
391 return;
392 }
393 if (!lengthLessDigest.equals(digest)) {
394 gDecodeFailures.push_back().appendf("Without using getLength, %s did not match digest "
395 "that uses getLength\n", srcPath);
396 }
397}
398#endif // defined(SK_BUILD_FOR_ANDROID) || defined(SK_BUILD_FOR_UNIX)
399
scroggo@google.com39edf4c2013-04-25 17:33:51 +0000400static void decodeFileAndWrite(const char srcPath[], const SkString* writePath) {
401 SkBitmap bitmap;
402 SkFILEStream stream(srcPath);
403 if (!stream.isValid()) {
404 gInvalidStreams.push_back().set(srcPath);
405 return;
406 }
407
408 SkImageDecoder* codec = SkImageDecoder::Factory(&stream);
409 if (NULL == codec) {
410 gMissingCodecs.push_back().set(srcPath);
411 return;
412 }
413
414 SkAutoTDelete<SkImageDecoder> ad(codec);
415
scroggo@google.com8d239242013-10-01 17:27:15 +0000416 codec->setSkipWritingZeroes(FLAGS_skip);
commit-bot@chromium.orgdac4a1d2013-10-08 19:40:18 +0000417 codec->setSampleSize(FLAGS_sampleSize);
scroggo@google.com39edf4c2013-04-25 17:33:51 +0000418 stream.rewind();
scroggo@google.com39edf4c2013-04-25 17:33:51 +0000419
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000420 // Create a string representing just the filename itself, for use in json expectations.
scroggo@google.comccd7afb2013-05-28 16:45:07 +0000421 SkString basename = SkOSPath::SkBasename(srcPath);
422 const char* filename = basename.c_str();
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000423
scroggo@google.com6d99de12013-08-06 18:56:53 +0000424 if (!codec->decode(&stream, &bitmap, gPrefConfig,
scroggo@google.combc91e8b2013-06-27 20:21:01 +0000425 SkImageDecoder::kDecodePixels_Mode)) {
scroggo@google.com36c5bdb2013-10-15 20:29:37 +0000426 if (NULL != gJsonExpectations.get()) {
427 skiagm::Expectations jsExpectations = gJsonExpectations->get(filename);
428 if (jsExpectations.ignoreFailure()) {
429 // This is a known failure.
430 gKnownFailures.push_back().appendf(
431 "failed to decode %s, which is a known failure.", srcPath);
432 return;
433 }
434 if (jsExpectations.empty()) {
435 // This is a failure, but it is a new file. Mark it as missing, with
436 // a note that it should be marked failing.
437 gMissingExpectations.push_back().appendf(
438 "new file %s (with no expectations) FAILED to decode.", srcPath);
439 return;
440 }
scroggo@google.combc91e8b2013-06-27 20:21:01 +0000441 }
scroggo@google.com36c5bdb2013-10-15 20:29:37 +0000442
443 // If there was a failure, and either there was no expectations file, or
444 // the expectations file listed a valid expectation, report the failure.
445 gDecodeFailures.push_back().set(srcPath);
scroggo@google.combc91e8b2013-06-27 20:21:01 +0000446 return;
447 }
448
scroggo@google.com6f67b3b2013-07-18 20:08:26 +0000449 // Test decoding just the bounds. The bounds should always match.
450 {
451 stream.rewind();
452 SkBitmap dim;
453 if (!codec->decode(&stream, &dim, SkImageDecoder::kDecodeBounds_Mode)) {
454 SkString failure = SkStringPrintf("failed to decode bounds for %s", srcPath);
455 gDecodeFailures.push_back() = failure;
456 } else {
457 // Now check that the bounds match:
458 if (dim.width() != bitmap.width() || dim.height() != bitmap.height()) {
459 SkString failure = SkStringPrintf("bounds do not match for %s", srcPath);
460 gDecodeFailures.push_back() = failure;
461 }
462 }
463 }
464
scroggo@google.comcd7a73c2013-08-28 18:33:31 +0000465 skiagm::GmResultDigest digest(bitmap);
466 if (compare_to_expectations_if_necessary(digest, filename,
scroggo@google.come339eb02013-08-06 18:51:30 +0000467 &gDecodeFailures,
scroggo@google.com36c5bdb2013-10-15 20:29:37 +0000468 &gMissingExpectations,
469 &gKnownFailures)) {
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000470 gSuccessfulDecodes.push_back().printf("%s [%d %d]", srcPath, bitmap.width(),
471 bitmap.height());
scroggo@google.comcf5eb6a2013-06-07 12:43:15 +0000472 } else if (!FLAGS_mismatchPath.isEmpty()) {
473 SkString outPath;
474 make_outname(&outPath, FLAGS_mismatchPath[0], srcPath, ".png");
475 if (write_bitmap(outPath.c_str(), bitmap)) {
476 gSuccessfulDecodes.push_back().appendf("\twrote %s", outPath.c_str());
477 } else {
478 gEncodeFailures.push_back().set(outPath);
479 }
480 }
481
scroggo@google.comcd7a73c2013-08-28 18:33:31 +0000482// FIXME: This test could be run on windows/mac once we remove their dependence on
483// getLength. See https://code.google.com/p/skia/issues/detail?id=1570
484#if defined(SK_BUILD_FOR_ANDROID) || defined(SK_BUILD_FOR_UNIX)
485 test_stream_without_length(srcPath, codec, digest);
486#endif
487
scroggo@google.comcf5eb6a2013-06-07 12:43:15 +0000488 if (writePath != NULL) {
489 SkString outPath;
490 make_outname(&outPath, writePath->c_str(), srcPath, ".png");
491 if (write_bitmap(outPath.c_str(), bitmap)) {
492 gSuccessfulDecodes.push_back().appendf("\twrote %s", outPath.c_str());
493 } else {
494 gEncodeFailures.push_back().set(outPath);
495 }
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000496 }
497
498 write_expectations(bitmap, filename);
scroggo@google.com39edf4c2013-04-25 17:33:51 +0000499
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000500 if (FLAGS_testSubsetDecoding) {
scroggo@google.com0018f752013-05-03 20:39:22 +0000501 SkDEBUGCODE(bool couldRewind =) stream.rewind();
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000502 SkASSERT(couldRewind);
503 int width, height;
504 // Build the tile index for decoding subsets. If the image is 1x1, skip subset
505 // decoding since there are no smaller subsets.
506 if (codec->buildTileIndex(&stream, &width, &height) && width > 1 && height > 1) {
507 SkASSERT(bitmap.width() == width && bitmap.height() == height);
508 // Call decodeSubset multiple times:
509 SkRandom rand(0);
510 for (int i = 0; i < 5; i++) {
511 SkBitmap bitmapFromDecodeSubset;
512 // FIXME: Come up with a more representative set of rectangles.
513 SkIRect rect = generate_random_rect(&rand, width, height);
514 SkString subsetDim = SkStringPrintf("[%d,%d,%d,%d]", rect.fLeft, rect.fTop,
515 rect.fRight, rect.fBottom);
scroggo@google.com6d99de12013-08-06 18:56:53 +0000516 if (codec->decodeSubset(&bitmapFromDecodeSubset, rect, gPrefConfig)) {
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000517 SkString subsetName = SkStringPrintf("%s_%s", filename, subsetDim.c_str());
scroggo@google.comcd7a73c2013-08-28 18:33:31 +0000518 skiagm::GmResultDigest subsetDigest(bitmapFromDecodeSubset);
519 if (compare_to_expectations_if_necessary(subsetDigest,
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000520 subsetName.c_str(),
scroggo@google.come339eb02013-08-06 18:51:30 +0000521 &gFailedSubsetDecodes,
scroggo@google.com36c5bdb2013-10-15 20:29:37 +0000522 &gMissingSubsetExpectations,
523 &gKnownSubsetFailures)) {
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000524 gSuccessfulSubsetDecodes.push_back().printf("Decoded subset %s from %s",
525 subsetDim.c_str(), srcPath);
scroggo@google.comcf5eb6a2013-06-07 12:43:15 +0000526 } else if (!FLAGS_mismatchPath.isEmpty()) {
527 write_subset(FLAGS_mismatchPath[0], filename, subsetDim.c_str(),
528 &bitmapFromDecodeSubset, rect, bitmap);
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000529 }
530
531 write_expectations(bitmapFromDecodeSubset, subsetName.c_str());
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000532 if (writePath != NULL) {
scroggo@google.com9da0e1d2013-05-15 18:14:36 +0000533 write_subset(writePath->c_str(), filename, subsetDim.c_str(),
534 &bitmapFromDecodeSubset, rect, bitmap);
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000535 }
536 } else {
scroggo@google.com9da0e1d2013-05-15 18:14:36 +0000537 gFailedSubsetDecodes.push_back().printf("Failed to decode region %s from %s",
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000538 subsetDim.c_str(), srcPath);
539 }
540 }
541 }
542 }
scroggo@google.com9da0e1d2013-05-15 18:14:36 +0000543
commit-bot@chromium.org546f70c2013-10-03 17:13:38 +0000544 // Do not attempt to re-encode A8, since our image encoders do not support encoding to A8.
545 if (FLAGS_reencode && bitmap.config() != SkBitmap::kA8_Config) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +0000546 // Encode to the format the file was originally in, or PNG if the encoder for the same
547 // format is unavailable.
548 SkImageDecoder::Format format = codec->getFormat();
549 if (SkImageDecoder::kUnknown_Format == format) {
550 if (stream.rewind()) {
551 format = SkImageDecoder::GetStreamFormat(&stream);
552 }
553 if (SkImageDecoder::kUnknown_Format == format) {
554 const char* dot = strrchr(srcPath, '.');
555 if (NULL != dot) {
556 format = guess_format_from_suffix(dot);
557 }
558 if (SkImageDecoder::kUnknown_Format == format) {
559 SkDebugf("Could not determine type for '%s'\n", srcPath);
560 format = SkImageDecoder::kPNG_Format;
561 }
562
563 }
564 } else {
565 SkASSERT(!stream.rewind() || SkImageDecoder::GetStreamFormat(&stream) == format);
566 }
567 SkImageEncoder::Type type = format_to_type(format);
568 // format should never be kUnknown_Format, so type should never be kUnknown_Type.
569 SkASSERT(type != SkImageEncoder::kUnknown_Type);
570
571 SkImageEncoder* encoder = SkImageEncoder::Create(type);
572 if (NULL == encoder) {
573 type = SkImageEncoder::kPNG_Type;
574 encoder = SkImageEncoder::Create(type);
575 SkASSERT(encoder);
576 }
577 SkAutoTDelete<SkImageEncoder> ade(encoder);
578 // Encode to a stream.
579 SkDynamicMemoryWStream wStream;
580 if (!encoder->encodeStream(&wStream, bitmap, 100)) {
581 gEncodeFailures.push_back().printf("Failed to reencode %s to type '%s'", srcPath,
582 suffix_for_type(type));
583 return;
584 }
585
586 SkAutoTUnref<SkData> data(wStream.copyToData());
587 if (writePath != NULL && type != SkImageEncoder::kPNG_Type) {
scroggo@google.comcf5eb6a2013-06-07 12:43:15 +0000588 // Write the encoded data to a file. Do not write to PNG, which was already written.
scroggo@google.com39edf4c2013-04-25 17:33:51 +0000589 SkString outPath;
590 make_outname(&outPath, writePath->c_str(), srcPath, suffix_for_type(type));
591 SkFILEWStream file(outPath.c_str());
592 if(file.write(data->data(), data->size())) {
593 gSuccessfulDecodes.push_back().appendf("\twrote %s", outPath.c_str());
594 } else {
595 gEncodeFailures.push_back().printf("Failed to write %s", outPath.c_str());
596 }
597 }
598 // Ensure that the reencoded data can still be decoded.
599 SkMemoryStream memStream(data);
600 SkBitmap redecodedBitmap;
601 SkImageDecoder::Format formatOnSecondDecode;
scroggo@google.com6d99de12013-08-06 18:56:53 +0000602 if (SkImageDecoder::DecodeStream(&memStream, &redecodedBitmap, gPrefConfig,
scroggo@google.com39edf4c2013-04-25 17:33:51 +0000603 SkImageDecoder::kDecodePixels_Mode,
604 &formatOnSecondDecode)) {
605 SkASSERT(format_to_type(formatOnSecondDecode) == type);
606 } else {
607 gDecodeFailures.push_back().printf("Failed to redecode %s after reencoding to '%s'",
608 srcPath, suffix_for_type(type));
609 }
610 }
scroggo@google.com39edf4c2013-04-25 17:33:51 +0000611}
612
613///////////////////////////////////////////////////////////////////////////////
614
scroggo@google.comb41ff952013-04-11 15:53:35 +0000615// If strings is not empty, print title, followed by each string on its own line starting
616// with a tab.
scroggo@google.com39edf4c2013-04-25 17:33:51 +0000617// @return bool True if strings had at least one entry.
618static bool print_strings(const char* title, const SkTArray<SkString, false>& strings) {
scroggo@google.comb41ff952013-04-11 15:53:35 +0000619 if (strings.count() > 0) {
620 SkDebugf("%s:\n", title);
621 for (int i = 0; i < strings.count(); i++) {
622 SkDebugf("\t%s\n", strings[i].c_str());
623 }
624 SkDebugf("\n");
scroggo@google.com39edf4c2013-04-25 17:33:51 +0000625 return true;
scroggo@google.comb41ff952013-04-11 15:53:35 +0000626 }
scroggo@google.com39edf4c2013-04-25 17:33:51 +0000627 return false;
reed@android.comaf459792009-04-24 19:52:53 +0000628}
629
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000630/**
631 * If directory is non null and does not end with a path separator, append one.
632 * @param directory SkString representing the path to a directory. If the last character is not a
633 * path separator (specific to the current OS), append one.
634 */
635static void append_path_separator_if_necessary(SkString* directory) {
636 if (directory != NULL && directory->c_str()[directory->size() - 1] != SkPATH_SEPARATOR) {
637 directory->appendf("%c", SkPATH_SEPARATOR);
638 }
639}
640
scroggo@google.com925cdca2013-06-28 20:04:42 +0000641/**
642 * Return true if the filename represents an image.
643 */
644static bool is_image_file(const char* filename) {
645 const char* gImageExtensions[] = {
646 ".png", ".PNG", ".jpg", ".JPG", ".jpeg", ".JPEG", ".bmp", ".BMP",
647 ".webp", ".WEBP", ".ico", ".ICO", ".wbmp", ".WBMP", ".gif", ".GIF"
648 };
649 for (size_t i = 0; i < SK_ARRAY_COUNT(gImageExtensions); ++i) {
650 if (SkStrEndsWith(filename, gImageExtensions[i])) {
651 return true;
652 }
653 }
654 return false;
655}
656
caryclark@google.com5987f582012-10-02 18:33:14 +0000657int tool_main(int argc, char** argv);
658int tool_main(int argc, char** argv) {
scroggo@google.comb41ff952013-04-11 15:53:35 +0000659 SkCommandLineFlags::SetUsage("Decode files, and optionally write the results to files.");
660 SkCommandLineFlags::Parse(argc, argv);
661
662 if (FLAGS_readPath.count() < 1) {
663 SkDebugf("Folder(s) or image(s) to decode are required.\n");
664 return -1;
665 }
666
667
reed@android.comaf459792009-04-24 19:52:53 +0000668 SkAutoGraphics ag;
scroggo@google.comb41ff952013-04-11 15:53:35 +0000669
scroggo@google.com3832da12013-06-19 19:12:53 +0000670 if (!FLAGS_readExpectationsPath.isEmpty() && sk_exists(FLAGS_readExpectationsPath[0])) {
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000671 gJsonExpectations.reset(SkNEW_ARGS(skiagm::JsonExpectationsSource,
672 (FLAGS_readExpectationsPath[0])));
673 }
674
reed@android.comaf459792009-04-24 19:52:53 +0000675 SkString outDir;
scroggo@google.comb41ff952013-04-11 15:53:35 +0000676 SkString* outDirPtr;
reed@android.comaf459792009-04-24 19:52:53 +0000677
scroggo@google.comb41ff952013-04-11 15:53:35 +0000678 if (FLAGS_writePath.count() == 1) {
679 outDir.set(FLAGS_writePath[0]);
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000680 append_path_separator_if_necessary(&outDir);
scroggo@google.comb41ff952013-04-11 15:53:35 +0000681 outDirPtr = &outDir;
682 } else {
683 outDirPtr = NULL;
684 }
685
scroggo@google.com6d99de12013-08-06 18:56:53 +0000686 if (FLAGS_config.count() == 1) {
687 // Only consider the first config specified on the command line.
688 const char* config = FLAGS_config[0];
689 if (0 == strcmp(config, "8888")) {
690 gPrefConfig = SkBitmap::kARGB_8888_Config;
691 } else if (0 == strcmp(config, "565")) {
692 gPrefConfig = SkBitmap::kRGB_565_Config;
693 } else if (0 == strcmp(config, "A8")) {
694 gPrefConfig = SkBitmap::kA8_Config;
695 } else if (0 != strcmp(config, "None")) {
696 SkDebugf("Invalid preferred config\n");
697 return -1;
698 }
699 }
700
scroggo@google.comb41ff952013-04-11 15:53:35 +0000701 for (int i = 0; i < FLAGS_readPath.count(); i++) {
scroggo@google.com2b9424b2013-06-21 19:12:47 +0000702 const char* readPath = FLAGS_readPath[i];
703 if (strlen(readPath) < 1) {
scroggo@google.comb41ff952013-04-11 15:53:35 +0000704 break;
705 }
scroggo@google.com2b9424b2013-06-21 19:12:47 +0000706 if (sk_isdir(readPath)) {
707 const char* dir = readPath;
708 SkOSFile::Iter iter(dir);
709 SkString filename;
710 while (iter.next(&filename)) {
scroggo@google.com925cdca2013-06-28 20:04:42 +0000711 if (!is_image_file(filename.c_str())) {
712 continue;
713 }
scroggo@google.com2b9424b2013-06-21 19:12:47 +0000714 SkString fullname = SkOSPath::SkPathJoin(dir, filename.c_str());
scroggo@google.comb41ff952013-04-11 15:53:35 +0000715 decodeFileAndWrite(fullname.c_str(), outDirPtr);
scroggo@google.com2b9424b2013-06-21 19:12:47 +0000716 }
scroggo@google.com925cdca2013-06-28 20:04:42 +0000717 } else if (sk_exists(readPath) && is_image_file(readPath)) {
scroggo@google.com2b9424b2013-06-21 19:12:47 +0000718 decodeFileAndWrite(readPath, outDirPtr);
reed@android.comaf459792009-04-24 19:52:53 +0000719 }
720 }
721
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000722 if (!FLAGS_createExpectationsPath.isEmpty()) {
723 // Use an empty value for everything besides expectations, since the reader only cares
724 // about the expectations.
725 Json::Value nullValue;
726 Json::Value root = skiagm::CreateJsonTree(gExpectationsToWrite, nullValue, nullValue,
727 nullValue, nullValue);
728 std::string jsonStdString = root.toStyledString();
scroggo@google.comcf5eb6a2013-06-07 12:43:15 +0000729 SkFILEWStream stream(FLAGS_createExpectationsPath[0]);
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000730 stream.write(jsonStdString.c_str(), jsonStdString.length());
731 }
scroggo@google.comb41ff952013-04-11 15:53:35 +0000732 // Add some space, since codecs may print warnings without newline.
733 SkDebugf("\n\n");
rmistry@google.comd6176b02012-08-23 18:14:13 +0000734
scroggo@google.com39edf4c2013-04-25 17:33:51 +0000735 bool failed = print_strings("Invalid files", gInvalidStreams);
736 failed |= print_strings("Missing codec", gMissingCodecs);
737 failed |= print_strings("Failed to decode", gDecodeFailures);
738 failed |= print_strings("Failed to encode", gEncodeFailures);
739 print_strings("Decoded", gSuccessfulDecodes);
scroggo@google.come339eb02013-08-06 18:51:30 +0000740 print_strings("Missing expectations", gMissingExpectations);
reed@android.comaf459792009-04-24 19:52:53 +0000741
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000742 if (FLAGS_testSubsetDecoding) {
743 failed |= print_strings("Failed subset decodes", gFailedSubsetDecodes);
744 print_strings("Decoded subsets", gSuccessfulSubsetDecodes);
scroggo@google.come339eb02013-08-06 18:51:30 +0000745 print_strings("Missing subset expectations", gMissingSubsetExpectations);
scroggo@google.com36c5bdb2013-10-15 20:29:37 +0000746 print_strings("Known subset failures", gKnownSubsetFailures);
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000747 }
748
scroggo@google.com36c5bdb2013-10-15 20:29:37 +0000749 print_strings("Known failures", gKnownFailures);
750
scroggo@google.com39edf4c2013-04-25 17:33:51 +0000751 return failed ? -1 : 0;
reed@android.comaf459792009-04-24 19:52:53 +0000752}
753
caryclark@google.com5987f582012-10-02 18:33:14 +0000754#if !defined SK_BUILD_FOR_IOS
755int main(int argc, char * const argv[]) {
756 return tool_main(argc, (char**) argv);
757}
758#endif