blob: 688d3a22753eb943d89245827eb78898dd541569 [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
reed@android.comaf459792009-04-24 19:52:53 +00008#include "SkBitmap.h"
scroggo@google.com39edf4c2013-04-25 17:33:51 +00009#include "SkColorPriv.h"
scroggo@google.comb41ff952013-04-11 15:53:35 +000010#include "SkCommandLineFlags.h"
scroggo@google.com39edf4c2013-04-25 17:33:51 +000011#include "SkData.h"
reed@android.comaf459792009-04-24 19:52:53 +000012#include "SkGraphics.h"
13#include "SkImageDecoder.h"
14#include "SkImageEncoder.h"
scroggo@google.comb41ff952013-04-11 15:53:35 +000015#include "SkOSFile.h"
scroggo@google.com7e6fcee2013-05-03 20:14:28 +000016#include "SkRandom.h"
reed@android.comaf459792009-04-24 19:52:53 +000017#include "SkStream.h"
scroggo@google.comb41ff952013-04-11 15:53:35 +000018#include "SkTArray.h"
reed@android.comaf459792009-04-24 19:52:53 +000019#include "SkTemplates.h"
20
scroggo@google.comb41ff952013-04-11 15:53:35 +000021DEFINE_string2(readPath, r, "", "Folder(s) and files to decode images. Required.");
22DEFINE_string2(writePath, w, "", "Write rendered images into this directory.");
scroggo@google.com39edf4c2013-04-25 17:33:51 +000023DEFINE_bool(reencode, true, "Reencode the images to test encoding.");
scroggo@google.com7e6fcee2013-05-03 20:14:28 +000024DEFINE_bool(testSubsetDecoding, true, "Test decoding subsets of images.");
scroggo@google.comb41ff952013-04-11 15:53:35 +000025
scroggo@google.com39edf4c2013-04-25 17:33:51 +000026struct Format {
27 SkImageEncoder::Type fType;
28 SkImageDecoder::Format fFormat;
29 const char* fSuffix;
30};
scroggo@google.comb41ff952013-04-11 15:53:35 +000031
scroggo@google.com39edf4c2013-04-25 17:33:51 +000032static const Format gFormats[] = {
33 { SkImageEncoder::kBMP_Type, SkImageDecoder::kBMP_Format, ".bmp" },
34 { SkImageEncoder::kGIF_Type, SkImageDecoder::kGIF_Format, ".gif" },
35 { SkImageEncoder::kICO_Type, SkImageDecoder::kICO_Format, ".ico" },
36 { SkImageEncoder::kJPEG_Type, SkImageDecoder::kJPEG_Format, ".jpg" },
37 { SkImageEncoder::kPNG_Type, SkImageDecoder::kPNG_Format, ".png" },
38 { SkImageEncoder::kWBMP_Type, SkImageDecoder::kWBMP_Format, ".wbmp" },
39 { SkImageEncoder::kWEBP_Type, SkImageDecoder::kWEBP_Format, ".webp" }
40};
41
42static SkImageEncoder::Type format_to_type(SkImageDecoder::Format format) {
43 for (size_t i = 0; i < SK_ARRAY_COUNT(gFormats); i++) {
44 if (gFormats[i].fFormat == format) {
45 return gFormats[i].fType;
46 }
reed@android.comaf459792009-04-24 19:52:53 +000047 }
scroggo@google.com39edf4c2013-04-25 17:33:51 +000048 return SkImageEncoder::kUnknown_Type;
reed@android.comaf459792009-04-24 19:52:53 +000049}
50
scroggo@google.com39edf4c2013-04-25 17:33:51 +000051static const char* suffix_for_type(SkImageEncoder::Type type) {
52 for (size_t i = 0; i < SK_ARRAY_COUNT(gFormats); i++) {
53 if (gFormats[i].fType == type) {
54 return gFormats[i].fSuffix;
55 }
56 }
57 return "";
58}
reed@android.comaf459792009-04-24 19:52:53 +000059
scroggo@google.com39edf4c2013-04-25 17:33:51 +000060static SkImageDecoder::Format guess_format_from_suffix(const char suffix[]) {
61 for (size_t i = 0; i < SK_ARRAY_COUNT(gFormats); i++) {
62 if (strcmp(suffix, gFormats[i].fSuffix) == 0) {
63 return gFormats[i].fFormat;
64 }
65 }
66 return SkImageDecoder::kUnknown_Format;
67}
68
69static void make_outname(SkString* dst, const char outDir[], const char src[],
70 const char suffix[]) {
reed@android.comaf459792009-04-24 19:52:53 +000071 dst->set(outDir);
72 const char* start = strrchr(src, '/');
73 if (start) {
74 start += 1; // skip the actual last '/'
75 } else {
76 start = src;
77 }
78 dst->append(start);
scroggo@google.com39edf4c2013-04-25 17:33:51 +000079 if (!dst->endsWith(suffix)) {
scroggo@google.comb41ff952013-04-11 15:53:35 +000080 const char* cstyleDst = dst->c_str();
81 const char* dot = strrchr(cstyleDst, '.');
82 if (dot != NULL) {
83 int32_t index = SkToS32(dot - cstyleDst);
84 dst->remove(index, dst->size() - index);
85 }
scroggo@google.com39edf4c2013-04-25 17:33:51 +000086 dst->append(suffix);
scroggo@google.comb41ff952013-04-11 15:53:35 +000087 }
88}
89
scroggo@google.com39edf4c2013-04-25 17:33:51 +000090// Store the names of the filenames to report later which ones failed, succeeded, and were
91// invalid.
92static SkTArray<SkString, false> gInvalidStreams;
93static SkTArray<SkString, false> gMissingCodecs;
94static SkTArray<SkString, false> gDecodeFailures;
95static SkTArray<SkString, false> gEncodeFailures;
96static SkTArray<SkString, false> gSuccessfulDecodes;
scroggo@google.com7e6fcee2013-05-03 20:14:28 +000097static SkTArray<SkString, false> gSuccessfulSubsetDecodes;
98static SkTArray<SkString, false> gFailedSubsetDecodes;
scroggo@google.com39edf4c2013-04-25 17:33:51 +000099
100static bool write_bitmap(const char outName[], SkBitmap* bm) {
101 SkBitmap bitmap8888;
102 if (SkBitmap::kARGB_8888_Config != bm->config()) {
103 if (!bm->copyTo(&bitmap8888, SkBitmap::kARGB_8888_Config)) {
104 return false;
105 }
106 bm = &bitmap8888;
107 }
108 // FIXME: This forces all pixels to be opaque, like the many implementations
109 // of force_all_opaque. These should be unified if they cannot be eliminated.
110 SkAutoLockPixels lock(*bm);
111 for (int y = 0; y < bm->height(); y++) {
112 for (int x = 0; x < bm->width(); x++) {
113 *bm->getAddr32(x, y) |= (SK_A32_MASK << SK_A32_SHIFT);
114 }
115 }
116 return SkImageEncoder::EncodeFile(outName, *bm, SkImageEncoder::kPNG_Type, 100);
117}
118
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000119/**
120 * Return a random SkIRect inside the range specified.
121 * @param rand Random number generator.
122 * @param maxX Exclusive maximum x-coordinate. SkIRect's fLeft and fRight will be
123 * in the range [0, maxX)
124 * @param maxY Exclusive maximum y-coordinate. SkIRect's fTop and fBottom will be
125 * in the range [0, maxY)
126 * @return SkIRect Non-empty, non-degenerate rectangle.
127 */
128static SkIRect generate_random_rect(SkRandom* rand, int32_t maxX, int32_t maxY) {
129 SkASSERT(maxX > 1 && maxY > 1);
130 int32_t left = rand->nextULessThan(maxX);
131 int32_t right = rand->nextULessThan(maxX);
132 int32_t top = rand->nextULessThan(maxY);
133 int32_t bottom = rand->nextULessThan(maxY);
134 SkIRect rect = SkIRect::MakeLTRB(left, top, right, bottom);
135 rect.sort();
136 // Make sure rect is not empty.
137 if (rect.fLeft == rect.fRight) {
138 if (rect.fLeft > 0) {
139 rect.fLeft--;
140 } else {
141 rect.fRight++;
142 // This branch is only taken if 0 == rect.fRight, and
143 // maxX must be at least 2, so it must still be in
144 // range.
145 SkASSERT(rect.fRight < maxX);
146 }
147 }
148 if (rect.fTop == rect.fBottom) {
149 if (rect.fTop > 0) {
150 rect.fTop--;
151 } else {
152 rect.fBottom++;
153 // Again, this must be in range.
154 SkASSERT(rect.fBottom < maxY);
155 }
156 }
157 return rect;
158}
159
scroggo@google.com39edf4c2013-04-25 17:33:51 +0000160static void decodeFileAndWrite(const char srcPath[], const SkString* writePath) {
161 SkBitmap bitmap;
162 SkFILEStream stream(srcPath);
163 if (!stream.isValid()) {
164 gInvalidStreams.push_back().set(srcPath);
165 return;
166 }
167
168 SkImageDecoder* codec = SkImageDecoder::Factory(&stream);
169 if (NULL == codec) {
170 gMissingCodecs.push_back().set(srcPath);
171 return;
172 }
173
174 SkAutoTDelete<SkImageDecoder> ad(codec);
175
176 stream.rewind();
177 if (!codec->decode(&stream, &bitmap, SkBitmap::kARGB_8888_Config,
178 SkImageDecoder::kDecodePixels_Mode)) {
179 gDecodeFailures.push_back().set(srcPath);
180 return;
181 }
182
183 gSuccessfulDecodes.push_back().printf("%s [%d %d]", srcPath, bitmap.width(), bitmap.height());
184
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000185 if (FLAGS_testSubsetDecoding) {
186 bool couldRewind = stream.rewind();
187 SkASSERT(couldRewind);
188 int width, height;
189 // Build the tile index for decoding subsets. If the image is 1x1, skip subset
190 // decoding since there are no smaller subsets.
191 if (codec->buildTileIndex(&stream, &width, &height) && width > 1 && height > 1) {
192 SkASSERT(bitmap.width() == width && bitmap.height() == height);
193 // Call decodeSubset multiple times:
194 SkRandom rand(0);
195 for (int i = 0; i < 5; i++) {
196 SkBitmap bitmapFromDecodeSubset;
197 // FIXME: Come up with a more representative set of rectangles.
198 SkIRect rect = generate_random_rect(&rand, width, height);
199 SkString subsetDim = SkStringPrintf("[%d,%d,%d,%d]", rect.fLeft, rect.fTop,
200 rect.fRight, rect.fBottom);
201 if (codec->decodeSubset(&bitmapFromDecodeSubset, rect, SkBitmap::kNo_Config)) {
202 gSuccessfulSubsetDecodes.push_back().printf("Decoded subset %s from %s",
203 subsetDim.c_str(), srcPath);
204 if (writePath != NULL) {
205 // Write the region to a file whose name includes the dimensions.
206 SkString suffix = SkStringPrintf("_%s.png", subsetDim.c_str());
207 SkString outPath;
208 make_outname(&outPath, writePath->c_str(), srcPath, suffix.c_str());
209 bool success = write_bitmap(outPath.c_str(), &bitmapFromDecodeSubset);
210 SkASSERT(success);
211 gSuccessfulSubsetDecodes.push_back().printf("\twrote %s", outPath.c_str());
212 // Also use extractSubset from the original for visual comparison.
213 SkBitmap extractedSubset;
214 if (bitmap.extractSubset(&extractedSubset, rect)) {
215 suffix.printf("_%s_extracted.png", subsetDim.c_str());
216 make_outname(&outPath, writePath->c_str(), srcPath, suffix.c_str());
217 success = write_bitmap(outPath.c_str(), &extractedSubset);
218 SkASSERT(success);
219 }
220 }
221 } else {
222 gFailedSubsetDecodes.push_back().printf("Failed to decode region %s from %s\n",
223 subsetDim.c_str(), srcPath);
224 }
225 }
226 }
227 }
scroggo@google.com39edf4c2013-04-25 17:33:51 +0000228 if (FLAGS_reencode) {
229 // Encode to the format the file was originally in, or PNG if the encoder for the same
230 // format is unavailable.
231 SkImageDecoder::Format format = codec->getFormat();
232 if (SkImageDecoder::kUnknown_Format == format) {
233 if (stream.rewind()) {
234 format = SkImageDecoder::GetStreamFormat(&stream);
235 }
236 if (SkImageDecoder::kUnknown_Format == format) {
237 const char* dot = strrchr(srcPath, '.');
238 if (NULL != dot) {
239 format = guess_format_from_suffix(dot);
240 }
241 if (SkImageDecoder::kUnknown_Format == format) {
242 SkDebugf("Could not determine type for '%s'\n", srcPath);
243 format = SkImageDecoder::kPNG_Format;
244 }
245
246 }
247 } else {
248 SkASSERT(!stream.rewind() || SkImageDecoder::GetStreamFormat(&stream) == format);
249 }
250 SkImageEncoder::Type type = format_to_type(format);
251 // format should never be kUnknown_Format, so type should never be kUnknown_Type.
252 SkASSERT(type != SkImageEncoder::kUnknown_Type);
253
254 SkImageEncoder* encoder = SkImageEncoder::Create(type);
255 if (NULL == encoder) {
256 type = SkImageEncoder::kPNG_Type;
257 encoder = SkImageEncoder::Create(type);
258 SkASSERT(encoder);
259 }
260 SkAutoTDelete<SkImageEncoder> ade(encoder);
261 // Encode to a stream.
262 SkDynamicMemoryWStream wStream;
263 if (!encoder->encodeStream(&wStream, bitmap, 100)) {
264 gEncodeFailures.push_back().printf("Failed to reencode %s to type '%s'", srcPath,
265 suffix_for_type(type));
266 return;
267 }
268
269 SkAutoTUnref<SkData> data(wStream.copyToData());
270 if (writePath != NULL && type != SkImageEncoder::kPNG_Type) {
271 // Write the encoded data to a file. Do not write to PNG, which will be written later,
272 // regardless of the input format.
273 SkString outPath;
274 make_outname(&outPath, writePath->c_str(), srcPath, suffix_for_type(type));
275 SkFILEWStream file(outPath.c_str());
276 if(file.write(data->data(), data->size())) {
277 gSuccessfulDecodes.push_back().appendf("\twrote %s", outPath.c_str());
278 } else {
279 gEncodeFailures.push_back().printf("Failed to write %s", outPath.c_str());
280 }
281 }
282 // Ensure that the reencoded data can still be decoded.
283 SkMemoryStream memStream(data);
284 SkBitmap redecodedBitmap;
285 SkImageDecoder::Format formatOnSecondDecode;
286 if (SkImageDecoder::DecodeStream(&memStream, &redecodedBitmap, SkBitmap::kNo_Config,
287 SkImageDecoder::kDecodePixels_Mode,
288 &formatOnSecondDecode)) {
289 SkASSERT(format_to_type(formatOnSecondDecode) == type);
290 } else {
291 gDecodeFailures.push_back().printf("Failed to redecode %s after reencoding to '%s'",
292 srcPath, suffix_for_type(type));
293 }
294 }
295
296 if (writePath != NULL) {
297 SkString outPath;
298 make_outname(&outPath, writePath->c_str(), srcPath, ".png");
299 if (write_bitmap(outPath.c_str(), &bitmap)) {
300 gSuccessfulDecodes.push_back().appendf("\twrote %s", outPath.c_str());
301 } else {
302 gEncodeFailures.push_back().set(outPath);
303 }
304 }
305}
306
307///////////////////////////////////////////////////////////////////////////////
308
scroggo@google.comb41ff952013-04-11 15:53:35 +0000309// If strings is not empty, print title, followed by each string on its own line starting
310// with a tab.
scroggo@google.com39edf4c2013-04-25 17:33:51 +0000311// @return bool True if strings had at least one entry.
312static bool print_strings(const char* title, const SkTArray<SkString, false>& strings) {
scroggo@google.comb41ff952013-04-11 15:53:35 +0000313 if (strings.count() > 0) {
314 SkDebugf("%s:\n", title);
315 for (int i = 0; i < strings.count(); i++) {
316 SkDebugf("\t%s\n", strings[i].c_str());
317 }
318 SkDebugf("\n");
scroggo@google.com39edf4c2013-04-25 17:33:51 +0000319 return true;
scroggo@google.comb41ff952013-04-11 15:53:35 +0000320 }
scroggo@google.com39edf4c2013-04-25 17:33:51 +0000321 return false;
reed@android.comaf459792009-04-24 19:52:53 +0000322}
323
caryclark@google.com5987f582012-10-02 18:33:14 +0000324int tool_main(int argc, char** argv);
325int tool_main(int argc, char** argv) {
scroggo@google.comb41ff952013-04-11 15:53:35 +0000326 SkCommandLineFlags::SetUsage("Decode files, and optionally write the results to files.");
327 SkCommandLineFlags::Parse(argc, argv);
328
329 if (FLAGS_readPath.count() < 1) {
330 SkDebugf("Folder(s) or image(s) to decode are required.\n");
331 return -1;
332 }
333
334
reed@android.comaf459792009-04-24 19:52:53 +0000335 SkAutoGraphics ag;
scroggo@google.comb41ff952013-04-11 15:53:35 +0000336
reed@android.comaf459792009-04-24 19:52:53 +0000337 SkString outDir;
scroggo@google.comb41ff952013-04-11 15:53:35 +0000338 SkString* outDirPtr;
reed@android.comaf459792009-04-24 19:52:53 +0000339
scroggo@google.comb41ff952013-04-11 15:53:35 +0000340 if (FLAGS_writePath.count() == 1) {
341 outDir.set(FLAGS_writePath[0]);
342 if (outDir.c_str()[outDir.size() - 1] != '/') {
343 outDir.append("/");
reed@android.comaf459792009-04-24 19:52:53 +0000344 }
scroggo@google.comb41ff952013-04-11 15:53:35 +0000345 outDirPtr = &outDir;
346 } else {
347 outDirPtr = NULL;
348 }
349
350 for (int i = 0; i < FLAGS_readPath.count(); i++) {
351 if (strlen(FLAGS_readPath[i]) < 1) {
352 break;
353 }
354 SkOSFile::Iter iter(FLAGS_readPath[i]);
355 SkString filename;
356 if (iter.next(&filename)) {
357 SkString directory(FLAGS_readPath[i]);
358 if (directory[directory.size() - 1] != '/') {
359 directory.append("/");
reed@android.comaf459792009-04-24 19:52:53 +0000360 }
scroggo@google.comb41ff952013-04-11 15:53:35 +0000361 do {
362 SkString fullname(directory);
363 fullname.append(filename);
364 decodeFileAndWrite(fullname.c_str(), outDirPtr);
365 } while (iter.next(&filename));
366 } else {
367 decodeFileAndWrite(FLAGS_readPath[i], outDirPtr);
reed@android.comaf459792009-04-24 19:52:53 +0000368 }
369 }
370
scroggo@google.comb41ff952013-04-11 15:53:35 +0000371 // Add some space, since codecs may print warnings without newline.
372 SkDebugf("\n\n");
rmistry@google.comd6176b02012-08-23 18:14:13 +0000373
scroggo@google.com39edf4c2013-04-25 17:33:51 +0000374 bool failed = print_strings("Invalid files", gInvalidStreams);
375 failed |= print_strings("Missing codec", gMissingCodecs);
376 failed |= print_strings("Failed to decode", gDecodeFailures);
377 failed |= print_strings("Failed to encode", gEncodeFailures);
378 print_strings("Decoded", gSuccessfulDecodes);
reed@android.comaf459792009-04-24 19:52:53 +0000379
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000380 if (FLAGS_testSubsetDecoding) {
381 failed |= print_strings("Failed subset decodes", gFailedSubsetDecodes);
382 print_strings("Decoded subsets", gSuccessfulSubsetDecodes);
383 }
384
scroggo@google.com39edf4c2013-04-25 17:33:51 +0000385 return failed ? -1 : 0;
reed@android.comaf459792009-04-24 19:52:53 +0000386}
387
caryclark@google.com5987f582012-10-02 18:33:14 +0000388#if !defined SK_BUILD_FOR_IOS
389int main(int argc, char * const argv[]) {
390 return tool_main(argc, (char**) argv);
391}
392#endif