Add the lazy decoder from PictureFlags to SkImageDecoder
[platform/upstream/libSkiaSharp.git] / tools / render_pictures_main.cpp
index 6066334..a2dff67 100644 (file)
@@ -5,72 +5,39 @@
  * found in the LICENSE file.
  */
 
+#include "LazyDecodeBitmap.h"
+#include "CopyTilesRenderer.h"
 #include "SkBitmap.h"
-#include "SkCanvas.h"
 #include "SkDevice.h"
+#include "SkCommandLineFlags.h"
 #include "SkGraphics.h"
+#include "SkImageDecoder.h"
+#include "SkImageEncoder.h"
 #include "SkMath.h"
 #include "SkOSFile.h"
 #include "SkPicture.h"
 #include "SkStream.h"
 #include "SkString.h"
-#include "SkTArray.h"
 #include "PictureRenderer.h"
+#include "PictureRenderingFlags.h"
 #include "picture_utils.h"
 
-static void usage(const char* argv0) {
-    SkDebugf("SkPicture rendering tool\n");
-    SkDebugf("\n"
-"Usage: \n"
-"     %s <input>... <outputDir> \n"
-"     [--mode pipe | pow2tile minWidth height[%] | simple\n"
-"         | tile width[%] height[%]]\n"
-"     [--device bitmap"
-#if SK_SUPPORT_GPU
-" | gpu"
-#endif
-"]"
-, argv0);
-    SkDebugf("\n\n");
-    SkDebugf(
-"     input:     A list of directories and files to use as input. Files are\n"
-"                expected to have the .skp extension.\n\n");
-    SkDebugf(
-"     outputDir: directory to write the rendered images.\n\n");
-    SkDebugf(
-"     --mode pipe | pow2tile minWidth height[%] | simple\n"
-"          | tile width[%] height[%]: Run in the corresponding mode.\n"
-"                                     Default is simple.\n");
-    SkDebugf(
-"                     pipe, Render using a SkGPipe.\n");
-    SkDebugf(
-"                     pow2tile minWidth height[%], Creates tiles with widths\n"
-"                                                  that are all a power of two\n"
-"                                                  such that they minimize the\n"
-"                                                  amount of wasted tile space.\n"
-"                                                  minWidth is the minimum width\n"
-"                                                  of these tiles and must be a\n"
-"                                                  power of two. A simple render\n"
-"                                                  is done with these tiles.\n");
-    SkDebugf(
-"                     simple, Render using the default rendering method.\n");
-    SkDebugf(
-"                     tile width[%] height[%], Do a simple render using tiles\n"
-"                                              with the given dimensions.\n");
-    SkDebugf("\n");
-    SkDebugf(
-"     --device bitmap"
-#if SK_SUPPORT_GPU
-" | gpu"
-#endif
-": Use the corresponding device. Default is bitmap.\n");
-    SkDebugf(
-"                     bitmap, Render to a bitmap.\n");
-#if SK_SUPPORT_GPU
-    SkDebugf(
-"                     gpu, Render to the GPU.\n");
-#endif
-}
+// Flags used by this file, alphabetically:
+DEFINE_int32(clone, 0, "Clone the picture n times before rendering.");
+DECLARE_bool(deferImageDecoding);
+DEFINE_int32(maxComponentDiff, 256, "Maximum diff on a component, 0 - 256. Components that differ "
+             "by more than this amount are considered errors, though all diffs are reported. "
+             "Requires --validate.");
+DECLARE_string(readPath);
+DEFINE_bool(writeEncodedImages, false, "Any time the skp contains an encoded image, write it to a "
+            "file rather than decoding it. Requires writePath to be set. Skips drawing the full "
+            "skp to a file. Not compatible with deferImageDecoding.");
+DEFINE_string2(writePath, w, "", "Directory to write the rendered images.");
+DEFINE_bool(writeWholeImage, false, "In tile mode, write the entire rendered image to a "
+            "file, instead of an image for each tile.");
+DEFINE_bool(validate, false, "Verify that the rendered image contains the same pixels as "
+            "the picture rendered in simple mode. When used in conjunction with --bbh, results "
+            "are validated against the picture rendered in the same mode, but without the bbh.");
 
 static void make_output_filepath(SkString* path, const SkString& dir,
                                  const SkString& name) {
@@ -79,8 +46,93 @@ static void make_output_filepath(SkString* path, const SkString& dir,
     path->remove(path->size() - 4, 4);
 }
 
-static bool render_picture(const SkString& inputPath, const SkString& outputDir,
-                           sk_tools::PictureRenderer& renderer) {
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ *  Table for translating from format of data to a suffix.
+ */
+struct Format {
+    SkImageDecoder::Format  fFormat;
+    const char*             fSuffix;
+};
+static const Format gFormats[] = {
+    { SkImageDecoder::kBMP_Format, ".bmp" },
+    { SkImageDecoder::kGIF_Format, ".gif" },
+    { SkImageDecoder::kICO_Format, ".ico" },
+    { SkImageDecoder::kJPEG_Format, ".jpg" },
+    { SkImageDecoder::kPNG_Format, ".png" },
+    { SkImageDecoder::kWBMP_Format, ".wbmp" },
+    { SkImageDecoder::kWEBP_Format, ".webp" },
+    { SkImageDecoder::kUnknown_Format, "" },
+};
+
+/**
+ *  Get an appropriate suffix for an image format.
+ */
+static const char* get_suffix_from_format(SkImageDecoder::Format format) {
+    for (size_t i = 0; i < SK_ARRAY_COUNT(gFormats); i++) {
+        if (gFormats[i].fFormat == format) {
+            return gFormats[i].fSuffix;
+        }
+    }
+    return "";
+}
+
+/**
+ *  Base name for an image file created from the encoded data in an skp.
+ */
+static SkString gInputFileName;
+
+/**
+ *  Number to be appended to the image file name so that it is unique.
+ */
+static uint32_t gImageNo;
+
+/**
+ *  Set up the name for writing encoded data to a file.
+ *  Sets gInputFileName to name, minus any extension ".*"
+ *  Sets gImageNo to 0, so images from file "X.skp" will
+ *  look like "X_<gImageNo>.<suffix>", beginning with 0
+ *  for each new skp.
+ */
+static void reset_image_file_base_name(const SkString& name) {
+    gImageNo = 0;
+    // Remove ".skp"
+    const char* cName = name.c_str();
+    const char* dot = strrchr(cName, '.');
+    if (dot != NULL) {
+        gInputFileName.set(cName, dot - cName);
+    } else {
+        gInputFileName.set(name);
+    }
+}
+
+/**
+ *  Write the raw encoded bitmap data to a file.
+ */
+static bool write_image_to_file(const void* buffer, size_t size, SkBitmap* bitmap) {
+    SkASSERT(!FLAGS_writePath.isEmpty());
+    SkMemoryStream memStream(buffer, size);
+    SkString outPath;
+    SkImageDecoder::Format format = SkImageDecoder::GetStreamFormat(&memStream);
+    SkString name = SkStringPrintf("%s_%d%s", gInputFileName.c_str(), gImageNo++,
+                                   get_suffix_from_format(format));
+    SkString dir(FLAGS_writePath[0]);
+    sk_tools::make_filepath(&outPath, dir, name);
+    SkFILEWStream fileStream(outPath.c_str());
+    if (!(fileStream.isValid() && fileStream.write(buffer, size))) {
+        SkDebugf("Failed to write encoded data to \"%s\"\n", outPath.c_str());
+    }
+    // Put in a dummy bitmap.
+    return SkImageDecoder::DecodeStream(&memStream, bitmap, SkBitmap::kNo_Config,
+                                        SkImageDecoder::kDecodeBounds_Mode);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+static bool render_picture(const SkString& inputPath, const SkString* outputDir,
+                           sk_tools::PictureRenderer& renderer,
+                           SkBitmap** out) {
     SkString inputFilename;
     sk_tools::get_basename(&inputFilename, inputPath);
 
@@ -91,213 +143,285 @@ static bool render_picture(const SkString& inputPath, const SkString& outputDir,
         return false;
     }
 
-    bool success = false;
-    SkPicture picture(&inputStream, &success);
-    if (!success) {
+    SkPicture::InstallPixelRefProc proc;
+    if (FLAGS_deferImageDecoding) {
+        proc = &sk_tools::LazyDecodeBitmap;
+    } else if (FLAGS_writeEncodedImages) {
+        SkASSERT(!FLAGS_writePath.isEmpty());
+        reset_image_file_base_name(inputFilename);
+        proc = &write_image_to_file;
+    } else {
+        proc = &SkImageDecoder::DecodeMemory;
+    }
+
+    SkDebugf("deserializing... %s\n", inputPath.c_str());
+
+    SkPicture* picture = SkPicture::CreateFromStream(&inputStream, proc);
+
+    if (NULL == picture) {
         SkDebugf("Could not read an SkPicture from %s\n", inputPath.c_str());
         return false;
     }
 
-    SkDebugf("drawing... [%i %i] %s\n", picture.width(), picture.height(),
-             inputPath.c_str());
+    for (int i = 0; i < FLAGS_clone; ++i) {
+        SkPicture* clone = picture->clone();
+        SkDELETE(picture);
+        picture = clone;
+    }
 
-    renderer.init(&picture);
+    SkDebugf("drawing... [%i %i] %s\n", picture->width(), picture->height(),
+             inputPath.c_str());
 
-    SkString outputPath;
-    make_output_filepath(&outputPath, outputDir, inputFilename);
+    renderer.init(picture);
+    renderer.setup();
 
-    success = renderer.render(&outputPath);
-    if (!success) {
-        SkDebugf("Could not write to file %s\n", outputPath.c_str());
+    SkString* outputPath = NULL;
+    if (NULL != outputDir && outputDir->size() > 0 && !FLAGS_writeEncodedImages) {
+        outputPath = SkNEW(SkString);
+        make_output_filepath(outputPath, *outputDir, inputFilename);
     }
 
-    renderer.resetState();
+    bool success = renderer.render(outputPath, out);
+    if (outputPath) {
+        if (!success) {
+            SkDebugf("Could not write to file %s\n", outputPath->c_str());
+        }
+        SkDELETE(outputPath);
+    }
 
     renderer.end();
+
+    SkDELETE(picture);
+    return success;
+}
+
+static inline int getByte(uint32_t value, int index) {
+    SkASSERT(0 <= index && index < 4);
+    return (value >> (index * 8)) & 0xFF;
+}
+
+static int MaxByteDiff(uint32_t v1, uint32_t v2) {
+    return SkMax32(SkMax32(abs(getByte(v1, 0) - getByte(v2, 0)), abs(getByte(v1, 1) - getByte(v2, 1))),
+                   SkMax32(abs(getByte(v1, 2) - getByte(v2, 2)), abs(getByte(v1, 3) - getByte(v2, 3))));
+}
+
+namespace {
+class AutoRestoreBbhType {
+public:
+    AutoRestoreBbhType() {
+        fRenderer = NULL;
+        fSavedBbhType = sk_tools::PictureRenderer::kNone_BBoxHierarchyType;
+    }
+
+    void set(sk_tools::PictureRenderer* renderer,
+             sk_tools::PictureRenderer::BBoxHierarchyType bbhType) {
+        fRenderer = renderer;
+        fSavedBbhType = renderer->getBBoxHierarchyType();
+        renderer->setBBoxHierarchyType(bbhType);
+    }
+
+    ~AutoRestoreBbhType() {
+        if (NULL != fRenderer) {
+            fRenderer->setBBoxHierarchyType(fSavedBbhType);
+        }
+    }
+
+private:
+    sk_tools::PictureRenderer* fRenderer;
+    sk_tools::PictureRenderer::BBoxHierarchyType fSavedBbhType;
+};
+}
+
+static bool render_picture(const SkString& inputPath, const SkString* outputDir,
+                           sk_tools::PictureRenderer& renderer) {
+    int diffs[256] = {0};
+    SkBitmap* bitmap = NULL;
+    bool success = render_picture(inputPath,
+        FLAGS_writeWholeImage ? NULL : outputDir,
+        renderer,
+        FLAGS_validate || FLAGS_writeWholeImage ? &bitmap : NULL);
+
+    if (!success || ((FLAGS_validate || FLAGS_writeWholeImage) && bitmap == NULL)) {
+        SkDebugf("Failed to draw the picture.\n");
+        SkDELETE(bitmap);
+        return false;
+    }
+
+    if (FLAGS_validate) {
+        SkBitmap* referenceBitmap = NULL;
+        sk_tools::PictureRenderer* referenceRenderer;
+        // If the renderer uses a BBoxHierarchy, then the reference renderer
+        // will be the same renderer, without the bbh.
+        AutoRestoreBbhType arbbh;
+        if (sk_tools::PictureRenderer::kNone_BBoxHierarchyType !=
+            renderer.getBBoxHierarchyType()) {
+            referenceRenderer = &renderer;
+            referenceRenderer->ref();  // to match auto unref below
+            arbbh.set(referenceRenderer, sk_tools::PictureRenderer::kNone_BBoxHierarchyType);
+        } else {
+            referenceRenderer = SkNEW(sk_tools::SimplePictureRenderer);
+        }
+        SkAutoTUnref<sk_tools::PictureRenderer> aurReferenceRenderer(referenceRenderer);
+
+        success = render_picture(inputPath, NULL, *referenceRenderer,
+                                 &referenceBitmap);
+
+        if (!success || NULL == referenceBitmap || NULL == referenceBitmap->getPixels()) {
+            SkDebugf("Failed to draw the reference picture.\n");
+            SkDELETE(bitmap);
+            SkDELETE(referenceBitmap);
+            return false;
+        }
+
+        if (success && (bitmap->width() != referenceBitmap->width())) {
+            SkDebugf("Expected image width: %i, actual image width %i.\n",
+                     referenceBitmap->width(), bitmap->width());
+            SkDELETE(bitmap);
+            SkDELETE(referenceBitmap);
+            return false;
+        }
+        if (success && (bitmap->height() != referenceBitmap->height())) {
+            SkDebugf("Expected image height: %i, actual image height %i",
+                     referenceBitmap->height(), bitmap->height());
+            SkDELETE(bitmap);
+            SkDELETE(referenceBitmap);
+            return false;
+        }
+
+        for (int y = 0; success && y < bitmap->height(); y++) {
+            for (int x = 0; success && x < bitmap->width(); x++) {
+                int diff = MaxByteDiff(*referenceBitmap->getAddr32(x, y),
+                                       *bitmap->getAddr32(x, y));
+                SkASSERT(diff >= 0 && diff <= 255);
+                diffs[diff]++;
+
+                if (diff > FLAGS_maxComponentDiff) {
+                    SkDebugf("Expected pixel at (%i %i) exceedds maximum "
+                                 "component diff of %i: 0x%x, actual 0x%x\n",
+                             x, y, FLAGS_maxComponentDiff,
+                             *referenceBitmap->getAddr32(x, y),
+                             *bitmap->getAddr32(x, y));
+                    SkDELETE(bitmap);
+                    SkDELETE(referenceBitmap);
+                    return false;
+                }
+            }
+        }
+        SkDELETE(referenceBitmap);
+
+        for (int i = 1; i <= 255; ++i) {
+            if(diffs[i] > 0) {
+                SkDebugf("Number of pixels with max diff of %i is %i\n", i, diffs[i]);
+            }
+        }
+    }
+
+    if (FLAGS_writeWholeImage) {
+        sk_tools::force_all_opaque(*bitmap);
+        if (NULL != outputDir && FLAGS_writeWholeImage) {
+            SkString inputFilename;
+            sk_tools::get_basename(&inputFilename, inputPath);
+            SkString outputPath;
+            make_output_filepath(&outputPath, *outputDir, inputFilename);
+            outputPath.append(".png");
+            if (!SkImageEncoder::EncodeFile(outputPath.c_str(), *bitmap,
+                                            SkImageEncoder::kPNG_Type, 100)) {
+                SkDebugf("Failed to draw the picture.\n");
+                success = false;
+            }
+        }
+    }
+    SkDELETE(bitmap);
+
     return success;
 }
 
-static int process_input(const SkString& input, const SkString& outputDir,
-                          sk_tools::PictureRenderer& renderer) {
-    SkOSFile::Iter iter(input.c_str(), "skp");
+
+static int process_input(const char* input, const SkString* outputDir,
+                         sk_tools::PictureRenderer& renderer) {
+    SkOSFile::Iter iter(input, "skp");
     SkString inputFilename;
     int failures = 0;
+    SkDebugf("process_input, %s\n", input);
     if (iter.next(&inputFilename)) {
         do {
             SkString inputPath;
-            sk_tools::make_filepath(&inputPath, input, inputFilename);
+            SkString inputAsSkString(input);
+            sk_tools::make_filepath(&inputPath, inputAsSkString, inputFilename);
             if (!render_picture(inputPath, outputDir, renderer)) {
                 ++failures;
             }
         } while(iter.next(&inputFilename));
-    } else if (SkStrEndsWith(input.c_str(), ".skp")) {
+    } else if (SkStrEndsWith(input, ".skp")) {
         SkString inputPath(input);
         if (!render_picture(inputPath, outputDir, renderer)) {
             ++failures;
         }
     } else {
         SkString warning;
-        warning.printf("Warning: skipping %s\n", input.c_str());
+        warning.printf("Warning: skipping %s\n", input);
         SkDebugf(warning.c_str());
     }
     return failures;
 }
 
-static void parse_commandline(int argc, char* const argv[], SkTArray<SkString>* inputs,
-                              sk_tools::PictureRenderer*& renderer){
-    const char* argv0 = argv[0];
-    char* const* stop = argv + argc;
-
-    sk_tools::PictureRenderer::SkDeviceTypes deviceType =
-        sk_tools::PictureRenderer::kBitmap_DeviceType;
-
-    for (++argv; argv < stop; ++argv) {
-        if (0 == strcmp(*argv, "--mode")) {
-            SkDELETE(renderer);
-
-            ++argv;
-            if (argv >= stop) {
-                SkDebugf("Missing mode for --mode\n");
-                usage(argv0);
-                exit(-1);
-            }
-
-            if (0 == strcmp(*argv, "pipe")) {
-                renderer = SkNEW(sk_tools::PipePictureRenderer);
-            } else if (0 == strcmp(*argv, "simple")) {
-                renderer = SkNEW(sk_tools::SimplePictureRenderer);
-            } else if ((0 == strcmp(*argv, "tile")) || (0 == strcmp(*argv, "pow2tile"))) {
-                char* mode = *argv;
-                bool isPowerOf2Mode = false;
-
-                if (0 == strcmp(*argv, "pow2tile")) {
-                    isPowerOf2Mode = true;
-                }
-
-                sk_tools::TiledPictureRenderer* tileRenderer =
-                    SkNEW(sk_tools::TiledPictureRenderer);
-                ++argv;
-                if (argv >= stop) {
-                    SkDELETE(tileRenderer);
-                    SkDebugf("Missing width for --mode %s\n", mode);
-                    usage(argv0);
-                    exit(-1);
-                }
-
-                if (isPowerOf2Mode) {
-                    int minWidth = atoi(*argv);
-
-                    if (!SkIsPow2(minWidth) || minWidth <= 0) {
-                        SkDELETE(tileRenderer);
-                        SkDebugf("--mode %s must be given a width"
-                                 " value that is a power of two\n", mode);
-                        exit(-1);
-                    }
-
-                    tileRenderer->setTileMinPowerOf2Width(minWidth);
-                } else if (sk_tools::is_percentage(*argv)) {
-                    tileRenderer->setTileWidthPercentage(atof(*argv));
-                    if (!(tileRenderer->getTileWidthPercentage() > 0)) {
-                        SkDELETE(tileRenderer);
-                        SkDebugf("--mode %s must be given a width percentage > 0\n", mode);
-                        exit(-1);
-                    }
-                } else {
-                    tileRenderer->setTileWidth(atoi(*argv));
-                    if (!(tileRenderer->getTileWidth() > 0)) {
-                        SkDELETE(tileRenderer);
-                        SkDebugf("--mode %s must be given a width > 0\n", mode);
-                        exit(-1);
-                    }
-                }
+int tool_main(int argc, char** argv);
+int tool_main(int argc, char** argv) {
+    SkCommandLineFlags::SetUsage("Render .skp files.");
+    SkCommandLineFlags::Parse(argc, argv);
 
-                ++argv;
-                if (argv >= stop) {
-                    SkDELETE(tileRenderer);
-                    SkDebugf("Missing height for --mode %s\n", mode);
-                    usage(argv0);
-                    exit(-1);
-                }
+    if (FLAGS_readPath.isEmpty()) {
+        SkDebugf(".skp files or directories are required.\n");
+        exit(-1);
+    }
 
-                if (sk_tools::is_percentage(*argv)) {
-                    tileRenderer->setTileHeightPercentage(atof(*argv));
-                    if (!(tileRenderer->getTileHeightPercentage() > 0)) {
-                        SkDELETE(tileRenderer);
-                        SkDebugf(
-                            "--mode %s must be given a height percentage > 0\n", mode);
-                        exit(-1);
-                    }
-                } else {
-                    tileRenderer->setTileHeight(atoi(*argv));
-                    if (!(tileRenderer->getTileHeight() > 0)) {
-                        SkDELETE(tileRenderer);
-                        SkDebugf("--mode %s must be given a height > 0\n", mode);
-                        exit(-1);
-                    }
-                }
+    if (FLAGS_maxComponentDiff < 0 || FLAGS_maxComponentDiff > 256) {
+        SkDebugf("--maxComponentDiff must be between 0 and 256\n");
+        exit(-1);
+    }
 
-                renderer = tileRenderer;
-            } else {
-                SkDebugf("%s is not a valid mode for --mode\n", *argv);
-                usage(argv0);
-                exit(-1);
-            }
-        } else if (0 == strcmp(*argv, "--device")) {
-            ++argv;
-            if (argv >= stop) {
-                SkDebugf("Missing mode for --deivce\n");
-                usage(argv0);
-                exit(-1);
-            }
+    if (FLAGS_maxComponentDiff != 256 && !FLAGS_validate) {
+        SkDebugf("--maxComponentDiff requires --validate\n");
+        exit(-1);
+    }
 
-            if (0 == strcmp(*argv, "bitmap")) {
-                deviceType = sk_tools::PictureRenderer::kBitmap_DeviceType;
-            }
-#if SK_SUPPORT_GPU
-            else if (0 == strcmp(*argv, "gpu")) {
-                deviceType = sk_tools::PictureRenderer::kGPU_DeviceType;
-            }
-#endif
-            else {
-                SkDebugf("%s is not a valid mode for --device\n", *argv);
-                usage(argv0);
-                exit(-1);
-            }
+    if (FLAGS_clone < 0) {
+        SkDebugf("--clone must be >= 0. Was %i\n", FLAGS_clone);
+        exit(-1);
+    }
 
-        } else if ((0 == strcmp(*argv, "-h")) || (0 == strcmp(*argv, "--help"))) {
-            SkDELETE(renderer);
-            usage(argv0);
+    if (FLAGS_writeEncodedImages) {
+        if (FLAGS_writePath.isEmpty()) {
+            SkDebugf("--writeEncodedImages requires --writePath\n");
+            exit(-1);
+        }
+        if (FLAGS_deferImageDecoding) {
+            SkDebugf("--writeEncodedImages is not compatible with --deferImageDecoding\n");
             exit(-1);
-        } else {
-            inputs->push_back(SkString(*argv));
         }
     }
 
-    if (inputs->count() < 2) {
-        SkDELETE(renderer);
-        usage(argv0);
-        exit(-1);
+    SkString errorString;
+    SkAutoTUnref<sk_tools::PictureRenderer> renderer(parseRenderer(errorString,
+                                                                   kRender_PictureTool));
+    if (errorString.size() > 0) {
+        SkDebugf("%s\n", errorString.c_str());
     }
 
-    if (NULL == renderer) {
-        renderer = SkNEW(sk_tools::SimplePictureRenderer);
+    if (renderer.get() == NULL) {
+        exit(-1);
     }
 
-    renderer->setDeviceType(deviceType);
-}
-
-int main(int argc, char* const argv[]) {
     SkAutoGraphics ag;
-    SkTArray<SkString> inputs;
-    sk_tools::PictureRenderer* renderer = NULL;
 
-    parse_commandline(argc, argv, &inputs, renderer);
-    SkString outputDir = inputs[inputs.count() - 1];
-    SkASSERT(renderer);
+    SkString outputDir;
+    if (FLAGS_writePath.count() == 1) {
+        outputDir.set(FLAGS_writePath[0]);
+    }
 
     int failures = 0;
-    for (int i = 0; i < inputs.count() - 1; i ++) {
-        failures += process_input(inputs[i], outputDir, *renderer);
+    for (int i = 0; i < FLAGS_readPath.count(); i ++) {
+        failures += process_input(FLAGS_readPath[i], &outputDir, *renderer.get());
     }
     if (failures != 0) {
         SkDebugf("Failed to render %i pictures.\n", failures);
@@ -312,6 +436,11 @@ int main(int argc, char* const argv[]) {
     }
 #endif
 #endif
+    return 0;
+}
 
-    SkDELETE(renderer);
+#if !defined SK_BUILD_FOR_IOS
+int main(int argc, char * const argv[]) {
+    return tool_main(argc, (char**) argv);
 }
+#endif