blob: 6ff5978f128c5b7316a241f7df24f8ef604b11aa [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.
96static SkTArray<SkString, false> gInvalidStreams;
97static SkTArray<SkString, false> gMissingCodecs;
98static SkTArray<SkString, false> gDecodeFailures;
99static SkTArray<SkString, false> gEncodeFailures;
100static SkTArray<SkString, false> gSuccessfulDecodes;
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000101static SkTArray<SkString, false> gSuccessfulSubsetDecodes;
102static SkTArray<SkString, false> gFailedSubsetDecodes;
scroggo@google.come339eb02013-08-06 18:51:30 +0000103// Files/subsets that do not have expectations. Not reported as a failure of the test so
104// the bots will not turn red with each new image test.
105static SkTArray<SkString, false> gMissingExpectations;
106static SkTArray<SkString, false> gMissingSubsetExpectations;
scroggo@google.com39edf4c2013-04-25 17:33:51 +0000107
scroggo@google.com6d99de12013-08-06 18:56:53 +0000108static SkBitmap::Config gPrefConfig(SkBitmap::kNo_Config);
109
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000110// Expections read from a file specified by readExpectationsPath. The expectations must have been
111// previously written using createExpectationsPath.
112SkAutoTUnref<skiagm::JsonExpectationsSource> gJsonExpectations;
113
scroggo@google.comcf5eb6a2013-06-07 12:43:15 +0000114static bool write_bitmap(const char outName[], const SkBitmap& bm) {
scroggo@google.comde09af32013-10-07 16:56:01 +0000115 if (SkImageEncoder::EncodeFile(outName, bm, SkImageEncoder::kPNG_Type, 100)) {
116 return true;
commit-bot@chromium.org546f70c2013-10-03 17:13:38 +0000117 }
scroggo@google.comde09af32013-10-07 16:56:01 +0000118
119 if (bm.config() == SkBitmap::kARGB_8888_Config) {
120 // First attempt at encoding failed, and the bitmap was already 8888. Making
121 // a copy is not going to help.
122 return false;
123 }
124
125 // Encoding failed. Copy to 8888 and try again.
126 SkBitmap bm8888;
127 if (!bm.copyTo(&bm8888, SkBitmap::kARGB_8888_Config)) {
128 return false;
129 }
130 return SkImageEncoder::EncodeFile(outName, bm8888, SkImageEncoder::kPNG_Type, 100);
scroggo@google.com39edf4c2013-04-25 17:33:51 +0000131}
132
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000133/**
134 * Return a random SkIRect inside the range specified.
135 * @param rand Random number generator.
136 * @param maxX Exclusive maximum x-coordinate. SkIRect's fLeft and fRight will be
137 * in the range [0, maxX)
138 * @param maxY Exclusive maximum y-coordinate. SkIRect's fTop and fBottom will be
139 * in the range [0, maxY)
140 * @return SkIRect Non-empty, non-degenerate rectangle.
141 */
142static SkIRect generate_random_rect(SkRandom* rand, int32_t maxX, int32_t maxY) {
143 SkASSERT(maxX > 1 && maxY > 1);
144 int32_t left = rand->nextULessThan(maxX);
145 int32_t right = rand->nextULessThan(maxX);
146 int32_t top = rand->nextULessThan(maxY);
147 int32_t bottom = rand->nextULessThan(maxY);
148 SkIRect rect = SkIRect::MakeLTRB(left, top, right, bottom);
149 rect.sort();
150 // Make sure rect is not empty.
151 if (rect.fLeft == rect.fRight) {
152 if (rect.fLeft > 0) {
153 rect.fLeft--;
154 } else {
155 rect.fRight++;
156 // This branch is only taken if 0 == rect.fRight, and
157 // maxX must be at least 2, so it must still be in
158 // range.
159 SkASSERT(rect.fRight < maxX);
160 }
161 }
162 if (rect.fTop == rect.fBottom) {
163 if (rect.fTop > 0) {
164 rect.fTop--;
165 } else {
166 rect.fBottom++;
167 // Again, this must be in range.
168 SkASSERT(rect.fBottom < maxY);
169 }
170 }
171 return rect;
172}
173
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000174// Stored expectations to be written to a file if createExpectationsPath is specified.
175static Json::Value gExpectationsToWrite;
176
177/**
epoger@google.comd4993ff2013-05-24 14:33:28 +0000178 * If expectations are to be recorded, record the bitmap expectations into global
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000179 * expectations array.
180 */
181static void write_expectations(const SkBitmap& bitmap, const char* filename) {
182 if (!FLAGS_createExpectationsPath.isEmpty()) {
183 // Creates an Expectations object, and add it to the list to write.
184 skiagm::Expectations expectation(bitmap);
185 Json::Value value = expectation.asJsonValue();
186 gExpectationsToWrite[filename] = value;
187 }
188}
189
190/**
scroggo@google.combc91e8b2013-06-27 20:21:01 +0000191 * Return true if this filename is a known failure, and therefore a failure
192 * to decode should be ignored.
193 */
194static bool expect_to_fail(const char* filename) {
195 if (NULL == gJsonExpectations.get()) {
196 return false;
197 }
198 skiagm::Expectations jsExpectations = gJsonExpectations->get(filename);
199 return jsExpectations.ignoreFailure();
200}
201
202/**
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000203 * Compare against an expectation for this filename, if there is one.
scroggo@google.comcd7a73c2013-08-28 18:33:31 +0000204 * @param digest GmResultDigest, computed from the decoded bitmap, to compare to the
205 * expectation.
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000206 * @param filename String used to find the expected value.
scroggo@google.come339eb02013-08-06 18:51:30 +0000207 * @param failureArray Array to add a failure message to on failure.
208 * @param missingArray Array to add missing expectation to on failure.
scroggo@google.com6ca30ca2013-05-14 17:30:17 +0000209 * @return bool True in any of these cases:
210 * - the bitmap matches the expectation.
scroggo@google.com6ca30ca2013-05-14 17:30:17 +0000211 * False in any of these cases:
scroggo@google.comcf5eb6a2013-06-07 12:43:15 +0000212 * - there is no expectations file.
scroggo@google.com6ca30ca2013-05-14 17:30:17 +0000213 * - there is an expectations file, but no expectation for this bitmap.
214 * - there is an expectation for this bitmap, but it did not match.
215 * - expectation could not be computed from the bitmap.
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000216 */
scroggo@google.comcd7a73c2013-08-28 18:33:31 +0000217static bool compare_to_expectations_if_necessary(const skiagm::GmResultDigest& digest,
218 const char* filename,
scroggo@google.come339eb02013-08-06 18:51:30 +0000219 SkTArray<SkString, false>* failureArray,
220 SkTArray<SkString, false>* missingArray) {
scroggo@google.comcd7a73c2013-08-28 18:33:31 +0000221 if (!digest.isValid()) {
scroggo@google.comcf5eb6a2013-06-07 12:43:15 +0000222 if (failureArray != NULL) {
223 failureArray->push_back().printf("decoded %s, but could not create a GmResultDigest.",
224 filename);
225 }
226 return false;
227 }
228
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000229 if (NULL == gJsonExpectations.get()) {
scroggo@google.comcf5eb6a2013-06-07 12:43:15 +0000230 return false;
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000231 }
232
233 skiagm::Expectations jsExpectation = gJsonExpectations->get(filename);
234 if (jsExpectation.empty()) {
scroggo@google.come339eb02013-08-06 18:51:30 +0000235 if (missingArray != NULL) {
236 missingArray->push_back().printf("decoded %s, but could not find expectation.",
scroggo@google.com6ca30ca2013-05-14 17:30:17 +0000237 filename);
238 }
239 return false;
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000240 }
241
scroggo@google.comcd7a73c2013-08-28 18:33:31 +0000242 if (jsExpectation.match(digest)) {
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000243 return true;
244 }
245
246 if (failureArray != NULL) {
247 failureArray->push_back().printf("decoded %s, but the result does not match "
248 "expectations.",
249 filename);
250 }
251 return false;
252}
253
scroggo@google.com9da0e1d2013-05-15 18:14:36 +0000254/**
255 * Helper function to write a bitmap subset to a file. Only called if subsets were created
256 * and a writePath was provided. Creates a subdirectory called 'subsets' and writes a PNG to
257 * that directory. Also creates a subdirectory called 'extracted' and writes a bitmap created
258 * using extractSubset to a PNG in that directory. Both files will represent the same
259 * subrectangle and have the same name for comparison.
260 * @param writePath Parent directory to hold the folders for the PNG files to write. Must
261 * not be NULL.
262 * @param filename Basename of the original file. Used to name the new files. Must not be
263 * NULL.
264 * @param subsetDim String representing the dimensions of the subset. Used to name the new
265 * files. Must not be NULL.
266 * @param bitmapFromDecodeSubset Pointer to SkBitmap created by SkImageDecoder::DecodeSubset,
267 * using rect as the area to decode.
268 * @param rect Rectangle of the area decoded into bitmapFromDecodeSubset. Used to call
269 * extractSubset on originalBitmap to create a bitmap with the same dimensions/pixels as
270 * bitmapFromDecodeSubset (assuming decodeSubset worked properly).
271 * @param originalBitmap SkBitmap decoded from the same stream as bitmapFromDecodeSubset,
272 * using SkImageDecoder::decode to get the entire image. Used to create a PNG file for
273 * comparison to the PNG created by bitmapFromDecodeSubset.
274 * @return bool Whether the function succeeded at drawing the decoded subset and the extracted
275 * subset to files.
276 */
277static bool write_subset(const char* writePath, const char* filename, const char* subsetDim,
278 SkBitmap* bitmapFromDecodeSubset, SkIRect rect,
279 const SkBitmap& originalBitmap) {
280 // All parameters must be valid.
281 SkASSERT(writePath != NULL);
282 SkASSERT(filename != NULL);
283 SkASSERT(subsetDim != NULL);
284 SkASSERT(bitmapFromDecodeSubset != NULL);
285
286 // Create a subdirectory to hold the results of decodeSubset.
scroggo@google.comccd7afb2013-05-28 16:45:07 +0000287 SkString dir = SkOSPath::SkPathJoin(writePath, "subsets");
scroggo@google.com9da0e1d2013-05-15 18:14:36 +0000288 if (!sk_mkdir(dir.c_str())) {
289 gFailedSubsetDecodes.push_back().printf("Successfully decoded %s from %s, but failed to "
290 "create a directory to write to.", subsetDim,
291 filename);
292 return false;
293 }
294
295 // Write the subset to a file whose name includes the dimensions.
296 SkString suffix = SkStringPrintf("_%s.png", subsetDim);
297 SkString outPath;
298 make_outname(&outPath, dir.c_str(), filename, suffix.c_str());
scroggo@google.comcf5eb6a2013-06-07 12:43:15 +0000299 SkAssertResult(write_bitmap(outPath.c_str(), *bitmapFromDecodeSubset));
scroggo@google.com9da0e1d2013-05-15 18:14:36 +0000300 gSuccessfulSubsetDecodes.push_back().printf("\twrote %s", outPath.c_str());
301
302 // Also use extractSubset from the original for visual comparison.
303 // Write the result to a file in a separate subdirectory.
304 SkBitmap extractedSubset;
305 if (!originalBitmap.extractSubset(&extractedSubset, rect)) {
306 gFailedSubsetDecodes.push_back().printf("Successfully decoded %s from %s, but failed to "
307 "extract a similar subset for comparison.",
308 subsetDim, filename);
309 return false;
310 }
311
scroggo@google.comccd7afb2013-05-28 16:45:07 +0000312 SkString dirExtracted = SkOSPath::SkPathJoin(writePath, "extracted");
scroggo@google.com9da0e1d2013-05-15 18:14:36 +0000313 if (!sk_mkdir(dirExtracted.c_str())) {
314 gFailedSubsetDecodes.push_back().printf("Successfully decoded %s from %s, but failed to "
315 "create a directory for extractSubset comparison.",
316 subsetDim, filename);
317 return false;
318 }
319
320 make_outname(&outPath, dirExtracted.c_str(), filename, suffix.c_str());
scroggo@google.comcf5eb6a2013-06-07 12:43:15 +0000321 SkAssertResult(write_bitmap(outPath.c_str(), extractedSubset));
scroggo@google.com9da0e1d2013-05-15 18:14:36 +0000322 return true;
323}
324
scroggo@google.comcd7a73c2013-08-28 18:33:31 +0000325// FIXME: This test could be run on windows/mac once we remove their dependence on
326// getLength. See https://code.google.com/p/skia/issues/detail?id=1570
327#if defined(SK_BUILD_FOR_ANDROID) || defined(SK_BUILD_FOR_UNIX)
328
329/**
330 * Dummy class for testing to ensure that a stream without a length decodes the same
331 * as a stream with a length.
332 */
333class FILEStreamWithoutLength : public SkFILEStream {
334public:
335 FILEStreamWithoutLength(const char path[])
336 : INHERITED(path) {}
337
338 virtual bool hasLength() const SK_OVERRIDE {
339 return false;
340 }
341
342private:
343 typedef SkFILEStream INHERITED;
344};
345
346/**
347 * Test that decoding a stream which reports to not have a length still results in the
348 * same image as if it did report to have a length. Assumes that codec was used to
349 * successfully decode the file using SkFILEStream.
350 * @param srcPath The path to the file, for recreating the length-less stream.
351 * @param codec The SkImageDecoder originally used to decode srcPath, which will be used
352 * again to decode the length-less stream.
353 * @param digest GmResultDigest computed from decoding the stream the first time.
354 * Decoding the length-less stream is expected to result in a matching digest.
355 */
356static void test_stream_without_length(const char srcPath[], SkImageDecoder* codec,
357 const skiagm::GmResultDigest& digest) {
358 if (!digest.isValid()) {
359 // An error was already reported.
360 return;
361 }
362 SkASSERT(srcPath);
363 SkASSERT(codec);
364 FILEStreamWithoutLength stream(srcPath);
365 // This will only be called after a successful decode. Creating a stream from the same
366 // path should never fail.
367 SkASSERT(stream.isValid());
368 SkBitmap bm;
369 if (!codec->decode(&stream, &bm, gPrefConfig, SkImageDecoder::kDecodePixels_Mode)) {
370 gDecodeFailures.push_back().appendf("Without using getLength, %s failed to decode\n",
371 srcPath);
372 return;
373 }
374 skiagm::GmResultDigest lengthLessDigest(bm);
375 if (!lengthLessDigest.isValid()) {
376 gDecodeFailures.push_back().appendf("Without using getLength, %s failed to build "
377 "a digest\n", srcPath);
378 return;
379 }
380 if (!lengthLessDigest.equals(digest)) {
381 gDecodeFailures.push_back().appendf("Without using getLength, %s did not match digest "
382 "that uses getLength\n", srcPath);
383 }
384}
385#endif // defined(SK_BUILD_FOR_ANDROID) || defined(SK_BUILD_FOR_UNIX)
386
scroggo@google.com39edf4c2013-04-25 17:33:51 +0000387static void decodeFileAndWrite(const char srcPath[], const SkString* writePath) {
388 SkBitmap bitmap;
389 SkFILEStream stream(srcPath);
390 if (!stream.isValid()) {
391 gInvalidStreams.push_back().set(srcPath);
392 return;
393 }
394
395 SkImageDecoder* codec = SkImageDecoder::Factory(&stream);
396 if (NULL == codec) {
397 gMissingCodecs.push_back().set(srcPath);
398 return;
399 }
400
401 SkAutoTDelete<SkImageDecoder> ad(codec);
402
scroggo@google.com8d239242013-10-01 17:27:15 +0000403 codec->setSkipWritingZeroes(FLAGS_skip);
commit-bot@chromium.orgdac4a1d2013-10-08 19:40:18 +0000404 codec->setSampleSize(FLAGS_sampleSize);
scroggo@google.com39edf4c2013-04-25 17:33:51 +0000405 stream.rewind();
scroggo@google.com39edf4c2013-04-25 17:33:51 +0000406
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000407 // Create a string representing just the filename itself, for use in json expectations.
scroggo@google.comccd7afb2013-05-28 16:45:07 +0000408 SkString basename = SkOSPath::SkBasename(srcPath);
409 const char* filename = basename.c_str();
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000410
scroggo@google.com6d99de12013-08-06 18:56:53 +0000411 if (!codec->decode(&stream, &bitmap, gPrefConfig,
scroggo@google.combc91e8b2013-06-27 20:21:01 +0000412 SkImageDecoder::kDecodePixels_Mode)) {
413 if (expect_to_fail(filename)) {
414 gSuccessfulDecodes.push_back().appendf(
415 "failed to decode %s, which is a known failure.", srcPath);
416 } else {
417 gDecodeFailures.push_back().set(srcPath);
418 }
419 return;
420 }
421
scroggo@google.com6f67b3b2013-07-18 20:08:26 +0000422 // Test decoding just the bounds. The bounds should always match.
423 {
424 stream.rewind();
425 SkBitmap dim;
426 if (!codec->decode(&stream, &dim, SkImageDecoder::kDecodeBounds_Mode)) {
427 SkString failure = SkStringPrintf("failed to decode bounds for %s", srcPath);
428 gDecodeFailures.push_back() = failure;
429 } else {
430 // Now check that the bounds match:
431 if (dim.width() != bitmap.width() || dim.height() != bitmap.height()) {
432 SkString failure = SkStringPrintf("bounds do not match for %s", srcPath);
433 gDecodeFailures.push_back() = failure;
434 }
435 }
436 }
437
scroggo@google.comcd7a73c2013-08-28 18:33:31 +0000438 skiagm::GmResultDigest digest(bitmap);
439 if (compare_to_expectations_if_necessary(digest, filename,
scroggo@google.come339eb02013-08-06 18:51:30 +0000440 &gDecodeFailures,
441 &gMissingExpectations)) {
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000442 gSuccessfulDecodes.push_back().printf("%s [%d %d]", srcPath, bitmap.width(),
443 bitmap.height());
scroggo@google.comcf5eb6a2013-06-07 12:43:15 +0000444 } else if (!FLAGS_mismatchPath.isEmpty()) {
445 SkString outPath;
446 make_outname(&outPath, FLAGS_mismatchPath[0], srcPath, ".png");
447 if (write_bitmap(outPath.c_str(), bitmap)) {
448 gSuccessfulDecodes.push_back().appendf("\twrote %s", outPath.c_str());
449 } else {
450 gEncodeFailures.push_back().set(outPath);
451 }
452 }
453
scroggo@google.comcd7a73c2013-08-28 18:33:31 +0000454// FIXME: This test could be run on windows/mac once we remove their dependence on
455// getLength. See https://code.google.com/p/skia/issues/detail?id=1570
456#if defined(SK_BUILD_FOR_ANDROID) || defined(SK_BUILD_FOR_UNIX)
457 test_stream_without_length(srcPath, codec, digest);
458#endif
459
scroggo@google.comcf5eb6a2013-06-07 12:43:15 +0000460 if (writePath != NULL) {
461 SkString outPath;
462 make_outname(&outPath, writePath->c_str(), srcPath, ".png");
463 if (write_bitmap(outPath.c_str(), bitmap)) {
464 gSuccessfulDecodes.push_back().appendf("\twrote %s", outPath.c_str());
465 } else {
466 gEncodeFailures.push_back().set(outPath);
467 }
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000468 }
469
470 write_expectations(bitmap, filename);
scroggo@google.com39edf4c2013-04-25 17:33:51 +0000471
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000472 if (FLAGS_testSubsetDecoding) {
scroggo@google.com0018f752013-05-03 20:39:22 +0000473 SkDEBUGCODE(bool couldRewind =) stream.rewind();
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000474 SkASSERT(couldRewind);
475 int width, height;
476 // Build the tile index for decoding subsets. If the image is 1x1, skip subset
477 // decoding since there are no smaller subsets.
478 if (codec->buildTileIndex(&stream, &width, &height) && width > 1 && height > 1) {
479 SkASSERT(bitmap.width() == width && bitmap.height() == height);
480 // Call decodeSubset multiple times:
481 SkRandom rand(0);
482 for (int i = 0; i < 5; i++) {
483 SkBitmap bitmapFromDecodeSubset;
484 // FIXME: Come up with a more representative set of rectangles.
485 SkIRect rect = generate_random_rect(&rand, width, height);
486 SkString subsetDim = SkStringPrintf("[%d,%d,%d,%d]", rect.fLeft, rect.fTop,
487 rect.fRight, rect.fBottom);
scroggo@google.com6d99de12013-08-06 18:56:53 +0000488 if (codec->decodeSubset(&bitmapFromDecodeSubset, rect, gPrefConfig)) {
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000489 SkString subsetName = SkStringPrintf("%s_%s", filename, subsetDim.c_str());
scroggo@google.comcd7a73c2013-08-28 18:33:31 +0000490 skiagm::GmResultDigest subsetDigest(bitmapFromDecodeSubset);
491 if (compare_to_expectations_if_necessary(subsetDigest,
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000492 subsetName.c_str(),
scroggo@google.come339eb02013-08-06 18:51:30 +0000493 &gFailedSubsetDecodes,
494 &gMissingSubsetExpectations)) {
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000495 gSuccessfulSubsetDecodes.push_back().printf("Decoded subset %s from %s",
496 subsetDim.c_str(), srcPath);
scroggo@google.comcf5eb6a2013-06-07 12:43:15 +0000497 } else if (!FLAGS_mismatchPath.isEmpty()) {
498 write_subset(FLAGS_mismatchPath[0], filename, subsetDim.c_str(),
499 &bitmapFromDecodeSubset, rect, bitmap);
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000500 }
501
502 write_expectations(bitmapFromDecodeSubset, subsetName.c_str());
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000503 if (writePath != NULL) {
scroggo@google.com9da0e1d2013-05-15 18:14:36 +0000504 write_subset(writePath->c_str(), filename, subsetDim.c_str(),
505 &bitmapFromDecodeSubset, rect, bitmap);
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000506 }
507 } else {
scroggo@google.com9da0e1d2013-05-15 18:14:36 +0000508 gFailedSubsetDecodes.push_back().printf("Failed to decode region %s from %s",
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000509 subsetDim.c_str(), srcPath);
510 }
511 }
512 }
513 }
scroggo@google.com9da0e1d2013-05-15 18:14:36 +0000514
commit-bot@chromium.org546f70c2013-10-03 17:13:38 +0000515 // Do not attempt to re-encode A8, since our image encoders do not support encoding to A8.
516 if (FLAGS_reencode && bitmap.config() != SkBitmap::kA8_Config) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +0000517 // Encode to the format the file was originally in, or PNG if the encoder for the same
518 // format is unavailable.
519 SkImageDecoder::Format format = codec->getFormat();
520 if (SkImageDecoder::kUnknown_Format == format) {
521 if (stream.rewind()) {
522 format = SkImageDecoder::GetStreamFormat(&stream);
523 }
524 if (SkImageDecoder::kUnknown_Format == format) {
525 const char* dot = strrchr(srcPath, '.');
526 if (NULL != dot) {
527 format = guess_format_from_suffix(dot);
528 }
529 if (SkImageDecoder::kUnknown_Format == format) {
530 SkDebugf("Could not determine type for '%s'\n", srcPath);
531 format = SkImageDecoder::kPNG_Format;
532 }
533
534 }
535 } else {
536 SkASSERT(!stream.rewind() || SkImageDecoder::GetStreamFormat(&stream) == format);
537 }
538 SkImageEncoder::Type type = format_to_type(format);
539 // format should never be kUnknown_Format, so type should never be kUnknown_Type.
540 SkASSERT(type != SkImageEncoder::kUnknown_Type);
541
542 SkImageEncoder* encoder = SkImageEncoder::Create(type);
543 if (NULL == encoder) {
544 type = SkImageEncoder::kPNG_Type;
545 encoder = SkImageEncoder::Create(type);
546 SkASSERT(encoder);
547 }
548 SkAutoTDelete<SkImageEncoder> ade(encoder);
549 // Encode to a stream.
550 SkDynamicMemoryWStream wStream;
551 if (!encoder->encodeStream(&wStream, bitmap, 100)) {
552 gEncodeFailures.push_back().printf("Failed to reencode %s to type '%s'", srcPath,
553 suffix_for_type(type));
554 return;
555 }
556
557 SkAutoTUnref<SkData> data(wStream.copyToData());
558 if (writePath != NULL && type != SkImageEncoder::kPNG_Type) {
scroggo@google.comcf5eb6a2013-06-07 12:43:15 +0000559 // Write the encoded data to a file. Do not write to PNG, which was already written.
scroggo@google.com39edf4c2013-04-25 17:33:51 +0000560 SkString outPath;
561 make_outname(&outPath, writePath->c_str(), srcPath, suffix_for_type(type));
562 SkFILEWStream file(outPath.c_str());
563 if(file.write(data->data(), data->size())) {
564 gSuccessfulDecodes.push_back().appendf("\twrote %s", outPath.c_str());
565 } else {
566 gEncodeFailures.push_back().printf("Failed to write %s", outPath.c_str());
567 }
568 }
569 // Ensure that the reencoded data can still be decoded.
570 SkMemoryStream memStream(data);
571 SkBitmap redecodedBitmap;
572 SkImageDecoder::Format formatOnSecondDecode;
scroggo@google.com6d99de12013-08-06 18:56:53 +0000573 if (SkImageDecoder::DecodeStream(&memStream, &redecodedBitmap, gPrefConfig,
scroggo@google.com39edf4c2013-04-25 17:33:51 +0000574 SkImageDecoder::kDecodePixels_Mode,
575 &formatOnSecondDecode)) {
576 SkASSERT(format_to_type(formatOnSecondDecode) == type);
577 } else {
578 gDecodeFailures.push_back().printf("Failed to redecode %s after reencoding to '%s'",
579 srcPath, suffix_for_type(type));
580 }
581 }
scroggo@google.com39edf4c2013-04-25 17:33:51 +0000582}
583
584///////////////////////////////////////////////////////////////////////////////
585
scroggo@google.comb41ff952013-04-11 15:53:35 +0000586// If strings is not empty, print title, followed by each string on its own line starting
587// with a tab.
scroggo@google.com39edf4c2013-04-25 17:33:51 +0000588// @return bool True if strings had at least one entry.
589static bool print_strings(const char* title, const SkTArray<SkString, false>& strings) {
scroggo@google.comb41ff952013-04-11 15:53:35 +0000590 if (strings.count() > 0) {
591 SkDebugf("%s:\n", title);
592 for (int i = 0; i < strings.count(); i++) {
593 SkDebugf("\t%s\n", strings[i].c_str());
594 }
595 SkDebugf("\n");
scroggo@google.com39edf4c2013-04-25 17:33:51 +0000596 return true;
scroggo@google.comb41ff952013-04-11 15:53:35 +0000597 }
scroggo@google.com39edf4c2013-04-25 17:33:51 +0000598 return false;
reed@android.comaf459792009-04-24 19:52:53 +0000599}
600
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000601/**
602 * If directory is non null and does not end with a path separator, append one.
603 * @param directory SkString representing the path to a directory. If the last character is not a
604 * path separator (specific to the current OS), append one.
605 */
606static void append_path_separator_if_necessary(SkString* directory) {
607 if (directory != NULL && directory->c_str()[directory->size() - 1] != SkPATH_SEPARATOR) {
608 directory->appendf("%c", SkPATH_SEPARATOR);
609 }
610}
611
scroggo@google.com925cdca2013-06-28 20:04:42 +0000612/**
613 * Return true if the filename represents an image.
614 */
615static bool is_image_file(const char* filename) {
616 const char* gImageExtensions[] = {
617 ".png", ".PNG", ".jpg", ".JPG", ".jpeg", ".JPEG", ".bmp", ".BMP",
618 ".webp", ".WEBP", ".ico", ".ICO", ".wbmp", ".WBMP", ".gif", ".GIF"
619 };
620 for (size_t i = 0; i < SK_ARRAY_COUNT(gImageExtensions); ++i) {
621 if (SkStrEndsWith(filename, gImageExtensions[i])) {
622 return true;
623 }
624 }
625 return false;
626}
627
caryclark@google.com5987f582012-10-02 18:33:14 +0000628int tool_main(int argc, char** argv);
629int tool_main(int argc, char** argv) {
scroggo@google.comb41ff952013-04-11 15:53:35 +0000630 SkCommandLineFlags::SetUsage("Decode files, and optionally write the results to files.");
631 SkCommandLineFlags::Parse(argc, argv);
632
633 if (FLAGS_readPath.count() < 1) {
634 SkDebugf("Folder(s) or image(s) to decode are required.\n");
635 return -1;
636 }
637
638
reed@android.comaf459792009-04-24 19:52:53 +0000639 SkAutoGraphics ag;
scroggo@google.comb41ff952013-04-11 15:53:35 +0000640
scroggo@google.com3832da12013-06-19 19:12:53 +0000641 if (!FLAGS_readExpectationsPath.isEmpty() && sk_exists(FLAGS_readExpectationsPath[0])) {
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000642 gJsonExpectations.reset(SkNEW_ARGS(skiagm::JsonExpectationsSource,
643 (FLAGS_readExpectationsPath[0])));
644 }
645
reed@android.comaf459792009-04-24 19:52:53 +0000646 SkString outDir;
scroggo@google.comb41ff952013-04-11 15:53:35 +0000647 SkString* outDirPtr;
reed@android.comaf459792009-04-24 19:52:53 +0000648
scroggo@google.comb41ff952013-04-11 15:53:35 +0000649 if (FLAGS_writePath.count() == 1) {
650 outDir.set(FLAGS_writePath[0]);
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000651 append_path_separator_if_necessary(&outDir);
scroggo@google.comb41ff952013-04-11 15:53:35 +0000652 outDirPtr = &outDir;
653 } else {
654 outDirPtr = NULL;
655 }
656
scroggo@google.com6d99de12013-08-06 18:56:53 +0000657 if (FLAGS_config.count() == 1) {
658 // Only consider the first config specified on the command line.
659 const char* config = FLAGS_config[0];
660 if (0 == strcmp(config, "8888")) {
661 gPrefConfig = SkBitmap::kARGB_8888_Config;
662 } else if (0 == strcmp(config, "565")) {
663 gPrefConfig = SkBitmap::kRGB_565_Config;
664 } else if (0 == strcmp(config, "A8")) {
665 gPrefConfig = SkBitmap::kA8_Config;
666 } else if (0 != strcmp(config, "None")) {
667 SkDebugf("Invalid preferred config\n");
668 return -1;
669 }
670 }
671
scroggo@google.comb41ff952013-04-11 15:53:35 +0000672 for (int i = 0; i < FLAGS_readPath.count(); i++) {
scroggo@google.com2b9424b2013-06-21 19:12:47 +0000673 const char* readPath = FLAGS_readPath[i];
674 if (strlen(readPath) < 1) {
scroggo@google.comb41ff952013-04-11 15:53:35 +0000675 break;
676 }
scroggo@google.com2b9424b2013-06-21 19:12:47 +0000677 if (sk_isdir(readPath)) {
678 const char* dir = readPath;
679 SkOSFile::Iter iter(dir);
680 SkString filename;
681 while (iter.next(&filename)) {
scroggo@google.com925cdca2013-06-28 20:04:42 +0000682 if (!is_image_file(filename.c_str())) {
683 continue;
684 }
scroggo@google.com2b9424b2013-06-21 19:12:47 +0000685 SkString fullname = SkOSPath::SkPathJoin(dir, filename.c_str());
scroggo@google.comb41ff952013-04-11 15:53:35 +0000686 decodeFileAndWrite(fullname.c_str(), outDirPtr);
scroggo@google.com2b9424b2013-06-21 19:12:47 +0000687 }
scroggo@google.com925cdca2013-06-28 20:04:42 +0000688 } else if (sk_exists(readPath) && is_image_file(readPath)) {
scroggo@google.com2b9424b2013-06-21 19:12:47 +0000689 decodeFileAndWrite(readPath, outDirPtr);
reed@android.comaf459792009-04-24 19:52:53 +0000690 }
691 }
692
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000693 if (!FLAGS_createExpectationsPath.isEmpty()) {
694 // Use an empty value for everything besides expectations, since the reader only cares
695 // about the expectations.
696 Json::Value nullValue;
697 Json::Value root = skiagm::CreateJsonTree(gExpectationsToWrite, nullValue, nullValue,
698 nullValue, nullValue);
699 std::string jsonStdString = root.toStyledString();
scroggo@google.comcf5eb6a2013-06-07 12:43:15 +0000700 SkFILEWStream stream(FLAGS_createExpectationsPath[0]);
scroggo@google.com6843bdb2013-05-08 19:14:23 +0000701 stream.write(jsonStdString.c_str(), jsonStdString.length());
702 }
scroggo@google.comb41ff952013-04-11 15:53:35 +0000703 // Add some space, since codecs may print warnings without newline.
704 SkDebugf("\n\n");
rmistry@google.comd6176b02012-08-23 18:14:13 +0000705
scroggo@google.com39edf4c2013-04-25 17:33:51 +0000706 bool failed = print_strings("Invalid files", gInvalidStreams);
707 failed |= print_strings("Missing codec", gMissingCodecs);
708 failed |= print_strings("Failed to decode", gDecodeFailures);
709 failed |= print_strings("Failed to encode", gEncodeFailures);
710 print_strings("Decoded", gSuccessfulDecodes);
scroggo@google.come339eb02013-08-06 18:51:30 +0000711 print_strings("Missing expectations", gMissingExpectations);
reed@android.comaf459792009-04-24 19:52:53 +0000712
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000713 if (FLAGS_testSubsetDecoding) {
714 failed |= print_strings("Failed subset decodes", gFailedSubsetDecodes);
715 print_strings("Decoded subsets", gSuccessfulSubsetDecodes);
scroggo@google.come339eb02013-08-06 18:51:30 +0000716 print_strings("Missing subset expectations", gMissingSubsetExpectations);
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000717 }
718
scroggo@google.com39edf4c2013-04-25 17:33:51 +0000719 return failed ? -1 : 0;
reed@android.comaf459792009-04-24 19:52:53 +0000720}
721
caryclark@google.com5987f582012-10-02 18:33:14 +0000722#if !defined SK_BUILD_FOR_IOS
723int main(int argc, char * const argv[]) {
724 return tool_main(argc, (char**) argv);
725}
726#endif