OSDN Git Service

Add PNG and 9-patch support
authorAdam Lesinski <adamlesinski@google.com>
Mon, 6 Apr 2015 18:46:52 +0000 (11:46 -0700)
committerAdam Lesinski <adamlesinski@google.com>
Fri, 10 Apr 2015 00:19:06 +0000 (17:19 -0700)
Change-Id: I9ecdfdf82b82d59084490da518e167e256afd5f2

tools/aapt2/Android.mk
tools/aapt2/Flag.cpp [new file with mode: 0644]
tools/aapt2/Flag.h [new file with mode: 0644]
tools/aapt2/Main.cpp
tools/aapt2/Png.cpp [new file with mode: 0644]
tools/aapt2/Png.h [new file with mode: 0644]
tools/aapt2/data/res/drawable/icon.png [new file with mode: 0644]
tools/aapt2/data/res/drawable/test.9.png [new file with mode: 0644]

index 9cea176..14f558e 100644 (file)
@@ -29,12 +29,14 @@ sources := \
        BinaryResourceParser.cpp \
        ConfigDescription.cpp \
        Files.cpp \
+       Flag.cpp \
        JavaClassGenerator.cpp \
        Linker.cpp \
        Locale.cpp \
        Logger.cpp \
        ManifestParser.cpp \
        ManifestValidator.cpp \
+       Png.cpp \
        ResChunkPullParser.cpp \
        Resolver.cpp \
        Resource.cpp \
@@ -69,7 +71,10 @@ testSources := \
        XliffXmlPullParser_test.cpp \
        XmlFlattener_test.cpp
 
-cIncludes :=
+cIncludes := \
+       external/libpng \
+       external/libz
+
 hostLdLibs :=
 
 hostStaticLibs := \
@@ -78,7 +83,8 @@ hostStaticLibs := \
        liblog \
        libcutils \
        libexpat \
-       libziparchive-host
+       libziparchive-host \
+       libpng
 
 ifneq ($(strip $(USE_MINGW)),)
        hostStaticLibs += libz
diff --git a/tools/aapt2/Flag.cpp b/tools/aapt2/Flag.cpp
new file mode 100644 (file)
index 0000000..b1ee8e7
--- /dev/null
@@ -0,0 +1,109 @@
+#include "Flag.h"
+#include "StringPiece.h"
+
+#include <functional>
+#include <iomanip>
+#include <iostream>
+#include <string>
+#include <vector>
+
+namespace aapt {
+namespace flag {
+
+struct Flag {
+    std::string name;
+    std::string description;
+    std::function<void(const StringPiece&)> action;
+    bool required;
+    bool* flagResult;
+    bool parsed;
+};
+
+static std::vector<Flag> sFlags;
+static std::vector<std::string> sArgs;
+
+void optionalFlag(const StringPiece& name, const StringPiece& description,
+                  std::function<void(const StringPiece&)> action) {
+    sFlags.push_back(
+            Flag{ name.toString(), description.toString(), action, false, nullptr, false });
+}
+
+void requiredFlag(const StringPiece& name, const StringPiece& description,
+                  std::function<void(const StringPiece&)> action) {
+    sFlags.push_back(
+            Flag{ name.toString(), description.toString(), action, true, nullptr, false });
+}
+
+void optionalSwitch(const StringPiece& name, const StringPiece& description, bool* result) {
+    sFlags.push_back(
+            Flag{ name.toString(), description.toString(), {}, false, result, false });
+}
+
+static void usageAndDie(const StringPiece& command) {
+    std::cerr << command << " [options]";
+    for (const Flag& flag : sFlags) {
+        if (flag.required) {
+            std::cerr << " " << flag.name << " arg";
+        }
+    }
+    std::cerr << " files..." << std::endl << std::endl << "Options:" << std::endl;
+
+    for (const Flag& flag : sFlags) {
+        std::string command = flag.name;
+        if (!flag.flagResult) {
+            command += " arg ";
+        }
+        std::cerr << "  " << std::setw(30) << std::left << command
+                  << flag.description << std::endl;
+    }
+    exit(1);
+}
+
+void parse(int argc, char** argv, const StringPiece& command) {
+    for (int i = 0; i < argc; i++) {
+        const StringPiece arg(argv[i]);
+        if (*arg.data() != '-') {
+            sArgs.emplace_back(arg.toString());
+            continue;
+        }
+
+        bool match = false;
+        for (Flag& flag : sFlags) {
+            if (arg == flag.name) {
+                match = true;
+                flag.parsed = true;
+                if (flag.flagResult) {
+                    *flag.flagResult = true;
+                } else {
+                    i++;
+                    if (i >= argc) {
+                        std::cerr << flag.name << " missing argument." << std::endl
+                                  << std::endl;
+                        usageAndDie(command);
+                    }
+                    flag.action(argv[i]);
+                }
+                break;
+            }
+        }
+
+        if (!match) {
+            std::cerr << "unknown option '" << arg << "'." << std::endl << std::endl;
+            usageAndDie(command);
+        }
+    }
+
+    for (const Flag& flag : sFlags) {
+        if (flag.required && !flag.parsed) {
+            std::cerr << "missing required flag " << flag.name << std::endl << std::endl;
+            usageAndDie(command);
+        }
+    }
+}
+
+const std::vector<std::string>& getArgs() {
+    return sArgs;
+}
+
+} // namespace flag
+} // namespace aapt
diff --git a/tools/aapt2/Flag.h b/tools/aapt2/Flag.h
new file mode 100644 (file)
index 0000000..32f5f2c
--- /dev/null
@@ -0,0 +1,28 @@
+#ifndef AAPT_FLAG_H
+#define AAPT_FLAG_H
+
+#include "StringPiece.h"
+
+#include <functional>
+#include <string>
+#include <vector>
+
+namespace aapt {
+namespace flag {
+
+void requiredFlag(const StringPiece& name, const StringPiece& description,
+                  std::function<void(const StringPiece&)> action);
+
+void optionalFlag(const StringPiece& name, const StringPiece& description,
+                  std::function<void(const StringPiece&)> action);
+
+void optionalSwitch(const StringPiece& name, const StringPiece& description, bool* result);
+
+void parse(int argc, char** argv, const StringPiece& command);
+
+const std::vector<std::string>& getArgs();
+
+} // namespace flag
+} // namespace aapt
+
+#endif // AAPT_FLAG_H
index cfc5874..3a4b444 100644 (file)
 #include "BigBuffer.h"
 #include "BinaryResourceParser.h"
 #include "Files.h"
+#include "Flag.h"
 #include "JavaClassGenerator.h"
 #include "Linker.h"
 #include "ManifestParser.h"
 #include "ManifestValidator.h"
+#include "Png.h"
 #include "ResourceParser.h"
 #include "ResourceTable.h"
 #include "ResourceValues.h"
@@ -41,6 +43,7 @@
 #include <iostream>
 #include <sstream>
 #include <sys/stat.h>
+#include <utils/Errors.h>
 
 using namespace aapt;
 
@@ -107,12 +110,12 @@ std::unique_ptr<FileReference> makeFileReference(StringPool& pool, const StringP
  * Collect files from 'root', filtering out any files that do not
  * match the FileFilter 'filter'.
  */
-bool walkTree(const StringPiece& root, const FileFilter& filter,
-        std::vector<Source>& outEntries) {
+bool walkTree(const Source& root, const FileFilter& filter,
+              std::vector<Source>* outEntries) {
     bool error = false;
 
-    for (const std::string& dirName : listFiles(root)) {
-        std::string dir(root.toString());
+    for (const std::string& dirName : listFiles(root.path)) {
+        std::string dir = root.path;
         appendPath(&dir, dirName);
 
         FileType ft = getFileType(dir);
@@ -134,13 +137,11 @@ bool walkTree(const StringPiece& root, const FileFilter& filter,
             }
 
             if (ft != FileType::kRegular) {
-                Logger::error(Source{ file })
-                    << "not a regular file."
-                    << std::endl;
+                Logger::error(Source{ file }) << "not a regular file." << std::endl;
                 error = true;
                 continue;
             }
-            outEntries.emplace_back(Source{ file });
+            outEntries->push_back(Source{ file });
         }
     }
     return !error;
@@ -171,9 +172,6 @@ bool loadBinaryResourceTable(std::shared_ptr<ResourceTable> table, const Source&
 }
 
 bool loadResTable(android::ResTable* table, const Source& source) {
-    // For NO_ERROR (which on Windows is a MACRO).
-    using namespace android;
-
     std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary);
     if (!ifs) {
         Logger::error(source) << strerror(errno) << std::endl;
@@ -190,7 +188,7 @@ bool loadResTable(android::ResTable* table, const Source& source) {
     char* buf = new char[dataSize];
     ifs.read(buf, dataSize);
 
-    bool result = table->add(buf, dataSize, -1, true) == NO_ERROR;
+    bool result = table->add(buf, dataSize, -1, true) == android::NO_ERROR;
 
     delete [] buf;
     return result;
@@ -323,13 +321,6 @@ bool collectXml(std::shared_ptr<ResourceTable> table, const Source& source,
         }
     }
 
-    std::unique_ptr<FileReference> fileResource = makeFileReference(
-            table->getValueStringPool(),
-            util::utf16ToUtf8(name.entry) + ".xml",
-            name.type,
-            config);
-    table->addResource(name, config, source.line(0), std::move(fileResource));
-
     for (size_t level : sdkLevels) {
         Logger::note(source)
                 << "creating v" << level << " versioned file."
@@ -347,14 +338,15 @@ bool collectXml(std::shared_ptr<ResourceTable> table, const Source& source,
     return true;
 }
 
-struct CompileXml {
+struct CompileItem {
     Source source;
     ResourceName name;
     ConfigDescription config;
+    std::string extension;
 };
 
-bool compileXml(std::shared_ptr<Resolver> resolver, const CompileXml& item,
-                const Source& outputSource, std::queue<CompileXml>* queue) {
+bool compileXml(std::shared_ptr<Resolver> resolver, const CompileItem& item,
+                const Source& outputSource, std::queue<CompileItem>* queue) {
     std::ifstream in(item.source.path, std::ifstream::binary);
     if (!in) {
         Logger::error(item.source) << strerror(errno) << std::endl;
@@ -376,7 +368,7 @@ bool compileXml(std::shared_ptr<Resolver> resolver, const CompileXml& item,
     if (minStrippedSdk.value() > 0) {
         // Something was stripped, so let's generate a new file
         // with the version of the smallest SDK version stripped.
-        CompileXml newWork = item;
+        CompileItem newWork = item;
         newWork.config.sdkVersion = minStrippedSdk.value();
         queue->push(newWork);
     }
@@ -394,9 +386,51 @@ bool compileXml(std::shared_ptr<Resolver> resolver, const CompileXml& item,
     return true;
 }
 
+bool compilePng(const Source& source, const Source& output) {
+    std::ifstream in(source.path, std::ifstream::binary);
+    if (!in) {
+        Logger::error(source) << strerror(errno) << std::endl;
+        return false;
+    }
+
+    std::ofstream out(output.path, std::ofstream::binary);
+    if (!out) {
+        Logger::error(output) << strerror(errno) << std::endl;
+        return false;
+    }
+
+    std::string err;
+    Png png;
+    if (!png.process(source, in, out, {}, &err)) {
+        Logger::error(source) << err << std::endl;
+        return false;
+    }
+    return true;
+}
+
+bool copyFile(const Source& source, const Source& output) {
+    std::ifstream in(source.path, std::ifstream::binary);
+    if (!in) {
+        Logger::error(source) << strerror(errno) << std::endl;
+        return false;
+    }
+
+    std::ofstream out(output.path, std::ofstream::binary);
+    if (!out) {
+        Logger::error(output) << strerror(errno) << std::endl;
+        return false;
+    }
+
+    if (out << in.rdbuf()) {
+        Logger::error(output) << strerror(errno) << std::endl;
+        return true;
+    }
+    return false;
+}
+
 struct AaptOptions {
     enum class Phase {
-        LegacyFull,
+        Full,
         Collect,
         Link,
         Compile,
@@ -411,16 +445,26 @@ struct AaptOptions {
     // The location of the manifest file.
     Source manifest;
 
-    // The files to process.
-    std::vector<Source> sources;
+    // The source directories to walk and find resource files.
+    std::vector<Source> sourceDirs;
+
+    // The resource files to process and collect.
+    std::vector<Source> collectFiles;
+
+    // The binary table files to link.
+    std::vector<Source> linkFiles;
+
+    // The resource files to compile.
+    std::vector<Source> compileFiles;
 
     // The libraries these files may reference.
     std::vector<Source> libraries;
 
-    // Output directory.
+    // Output path. This can be a directory or file
+    // depending on the phase.
     Source output;
 
-    // Whether to generate a Java Class.
+    // Directory to in which to generate R.java.
     Maybe<Source> generateJavaClass;
 
     // Whether to output verbose details about
@@ -428,9 +472,8 @@ struct AaptOptions {
     bool verbose = false;
 };
 
-bool compileAndroidManifest(std::shared_ptr<Resolver> resolver, const AaptOptions& options) {
-    using namespace android;
-
+bool compileAndroidManifest(const std::shared_ptr<Resolver>& resolver,
+                            const AaptOptions& options) {
     Source outSource = options.output;
     appendPath(&outSource.path, "AndroidManifest.xml");
 
@@ -461,8 +504,8 @@ bool compileAndroidManifest(std::shared_ptr<Resolver> resolver, const AaptOption
         p += b.size;
     }
 
-    ResXMLTree tree;
-    if (tree.setTo(data.get(), outBuffer.size()) != NO_ERROR) {
+    android::ResXMLTree tree;
+    if (tree.setTo(data.get(), outBuffer.size()) != android::NO_ERROR) {
         return false;
     }
 
@@ -496,246 +539,117 @@ bool loadAppInfo(const Source& source, AppInfo* outInfo) {
     return parser.parse(source, pullParser, outInfo);
 }
 
-/**
- * Parses legacy options and walks the source directories collecting
- * files to process.
- */
-bool prepareLegacy(std::vector<StringPiece>::const_iterator argsIter,
-        const std::vector<StringPiece>::const_iterator argsEndIter,
-        AaptOptions &options) {
-    options.phase = AaptOptions::Phase::LegacyFull;
-
-    std::vector<StringPiece> sourceDirs;
-    while (argsIter != argsEndIter) {
-        if (*argsIter == "-S") {
-            ++argsIter;
-            if (argsIter == argsEndIter) {
-                Logger::error() << "-S missing argument." << std::endl;
-                return false;
-            }
-            sourceDirs.push_back(*argsIter);
-        } else if (*argsIter == "-I") {
-            ++argsIter;
-            if (argsIter == argsEndIter) {
-                Logger::error() << "-I missing argument." << std::endl;
-                return false;
-            }
-            options.libraries.push_back(Source{ argsIter->toString() });
-        } else if (*argsIter == "-M") {
-            ++argsIter;
-            if (argsIter == argsEndIter) {
-                Logger::error() << "-M missing argument." << std::endl;
-                return false;
-            }
+static AaptOptions prepareArgs(int argc, char** argv) {
+    if (argc < 2) {
+        std::cerr << "no command specified." << std::endl;
+        exit(1);
+    }
 
-            if (!options.manifest.path.empty()) {
-                Logger::error() << "multiple -M flags are not allowed." << std::endl;
-                return false;
-            }
-            options.manifest.path = argsIter->toString();
-        } else if (*argsIter == "-o") {
-            ++argsIter;
-            if (argsIter == argsEndIter) {
-                Logger::error() << "-o missing argument." << std::endl;
-                return false;
-            }
-            options.output = Source{ argsIter->toString() };
-        } else if (*argsIter == "-J") {
-            ++argsIter;
-            if (argsIter == argsEndIter) {
-                Logger::error() << "-J missing argument." << std::endl;
-                return false;
-            }
-            options.generateJavaClass = make_value<Source>(Source{ argsIter->toString() });
-        } else if (*argsIter == "-v") {
-            options.verbose = true;
-        } else {
-            Logger::error() << "unrecognized option '" << *argsIter << "'." << std::endl;
-            return false;
-        }
+    const StringPiece command(argv[1]);
+    argc -= 2;
+    argv += 2;
 
-        ++argsIter;
-    }
+    AaptOptions options;
 
-    if (options.manifest.path.empty()) {
-        Logger::error() << "must specify manifest file with -M." << std::endl;
-        return false;
+    StringPiece outputDescription = "place output in file";
+    if (command == "package") {
+        options.phase = AaptOptions::Phase::Full;
+        outputDescription = "place output in directory";
+    } else if (command == "collect") {
+        options.phase = AaptOptions::Phase::Collect;
+    } else if (command == "link") {
+        options.phase = AaptOptions::Phase::Link;
+    } else if (command == "compile") {
+        options.phase = AaptOptions::Phase::Compile;
+        outputDescription = "place output in directory";
+    } else {
+        std::cerr << "invalid command '" << command << "'." << std::endl;
+        exit(1);
     }
 
-    // Load the App's package name, etc.
-    if (!loadAppInfo(options.manifest, &options.appInfo)) {
-        return false;
-    }
+    if (options.phase == AaptOptions::Phase::Full) {
+        flag::requiredFlag("-S", "add a directory in which to find resources",
+                [&options](const StringPiece& arg) {
+                    options.sourceDirs.push_back(Source{ arg.toString() });
+                });
+
+        flag::requiredFlag("-M", "path to AndroidManifest.xml",
+                [&options](const StringPiece& arg) {
+                    options.manifest = Source{ arg.toString() };
+                });
+
+        flag::optionalFlag("-I", "add an Android APK to link against",
+                [&options](const StringPiece& arg) {
+                    options.libraries.push_back(Source{ arg.toString() });
+                });
+
+        flag::optionalFlag("--java", "directory in which to generate R.java",
+                [&options](const StringPiece& arg) {
+                    options.generateJavaClass = Source{ arg.toString() };
+                });
 
-    /**
-     * Set up the file filter to ignore certain files.
-     */
-    const char* customIgnore = getenv("ANDROID_AAPT_IGNORE");
-    FileFilter fileFilter;
-    if (customIgnore && customIgnore[0]) {
-        fileFilter.setPattern(customIgnore);
     } else {
-        fileFilter.setPattern(
-                "!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~");
-    }
+        flag::requiredFlag("--package", "Android package name",
+                [&options](const StringPiece& arg) {
+                    options.appInfo.package = util::utf8ToUtf16(arg);
+                });
 
-    /*
-     * Enumerate the files in each source directory.
-     */
-    for (const StringPiece& source : sourceDirs) {
-        if (!walkTree(source, fileFilter, options.sources)) {
-            return false;
+        if (options.phase != AaptOptions::Phase::Collect) {
+            flag::optionalFlag("-I", "add an Android APK to link against",
+                    [&options](const StringPiece& arg) {
+                        options.libraries.push_back(Source{ arg.toString() });
+                    });
         }
-    }
-    return true;
-}
-
-bool prepareCollect(std::vector<StringPiece>::const_iterator argsIter,
-        const std::vector<StringPiece>::const_iterator argsEndIter,
-        AaptOptions& options) {
-    options.phase = AaptOptions::Phase::Collect;
 
-    while (argsIter != argsEndIter) {
-        if (*argsIter == "--package") {
-            ++argsIter;
-            if (argsIter == argsEndIter) {
-                Logger::error() << "--package missing argument." << std::endl;
-                return false;
-            }
-            options.appInfo.package = util::utf8ToUtf16(*argsIter);
-        } else if (*argsIter == "-o") {
-            ++argsIter;
-            if (argsIter == argsEndIter) {
-                Logger::error() << "-o missing argument." << std::endl;
-                return false;
-            }
-            options.output = Source{ argsIter->toString() };
-        } else if (*argsIter == "-v") {
-            options.verbose = true;
-        } else if (argsIter->data()[0] != '-') {
-            options.sources.push_back(Source{ argsIter->toString() });
-        } else {
-            Logger::error()
-                    << "unknown option '"
-                    << *argsIter
-                    << "'."
-                    << std::endl;
-            return false;
+        if (options.phase == AaptOptions::Phase::Link) {
+            flag::optionalFlag("--java", "directory in which to generate R.java",
+                    [&options](const StringPiece& arg) {
+                        options.generateJavaClass = Source{ arg.toString() };
+                    });
         }
-        ++argsIter;
     }
-    return true;
-}
 
-bool prepareLink(std::vector<StringPiece>::const_iterator argsIter,
-        const std::vector<StringPiece>::const_iterator argsEndIter,
-        AaptOptions& options) {
-    options.phase = AaptOptions::Phase::Link;
+    // Common flags for all steps.
+    flag::requiredFlag("-o", outputDescription, [&options](const StringPiece& arg) {
+        options.output = Source{ arg.toString() };
+    });
+    flag::optionalSwitch("-v", "enables verbose logging", &options.verbose);
 
-    while (argsIter != argsEndIter) {
-        if (*argsIter == "--package") {
-            ++argsIter;
-            if (argsIter == argsEndIter) {
-                Logger::error() << "--package missing argument." << std::endl;
-                return false;
-            }
-            options.appInfo.package = util::utf8ToUtf16(*argsIter);
-        } else if (*argsIter == "-o") {
-            ++argsIter;
-            if (argsIter == argsEndIter) {
-                Logger::error() << "-o missing argument." << std::endl;
-                return false;
-            }
-            options.output = Source{ argsIter->toString() };
-        } else if (*argsIter == "-I") {
-            ++argsIter;
-            if (argsIter == argsEndIter) {
-                Logger::error() << "-I missing argument." << std::endl;
-                return false;
-            }
-            options.libraries.push_back(Source{ argsIter->toString() });
-        } else if (*argsIter == "--java") {
-            ++argsIter;
-            if (argsIter == argsEndIter) {
-                Logger::error() << "--java missing argument." << std::endl;
-                return false;
-            }
-            options.generateJavaClass = make_value<Source>(Source{ argsIter->toString() });
-        } else if (*argsIter == "-v") {
-            options.verbose = true;
-        } else if (argsIter->data()[0] != '-') {
-            options.sources.push_back(Source{ argsIter->toString() });
-        } else {
-            Logger::error()
-                    << "unknown option '"
-                    << *argsIter
-                    << "'."
-                    << std::endl;
-            return false;
-        }
-        ++argsIter;
-    }
-    return true;
-}
+    // Build the command string for output (eg. "aapt2 compile").
+    std::string fullCommand = "aapt2";
+    fullCommand += " ";
+    fullCommand += command.toString();
 
-bool prepareCompile(std::vector<StringPiece>::const_iterator argsIter,
-        const std::vector<StringPiece>::const_iterator argsEndIter,
-        AaptOptions& options) {
-    options.phase = AaptOptions::Phase::Compile;
+    // Actually read the command line flags.
+    flag::parse(argc, argv, fullCommand);
 
-    while (argsIter != argsEndIter) {
-        if (*argsIter == "--package") {
-            ++argsIter;
-            if (argsIter == argsEndIter) {
-                Logger::error() << "--package missing argument." << std::endl;
-                return false;
-            }
-            options.appInfo.package = util::utf8ToUtf16(*argsIter);
-        } else if (*argsIter == "-o") {
-            ++argsIter;
-            if (argsIter == argsEndIter) {
-                Logger::error() << "-o missing argument." << std::endl;
-                return false;
-            }
-            options.output = Source{ argsIter->toString() };
-        } else if (*argsIter == "-I") {
-            ++argsIter;
-            if (argsIter == argsEndIter) {
-                Logger::error() << "-I missing argument." << std::endl;
-                return false;
-            }
-            options.libraries.push_back(Source{ argsIter->toString() });
-        } else if (*argsIter == "-v") {
-            options.verbose = true;
-        } else if (argsIter->data()[0] != '-') {
-            options.sources.push_back(Source{ argsIter->toString() });
-        } else {
-            Logger::error()
-                    << "unknown option '"
-                    << *argsIter
-                    << "'."
-                    << std::endl;
-            return false;
+    // Copy all the remaining arguments.
+    if (options.phase == AaptOptions::Phase::Collect) {
+        for (const std::string& arg : flag::getArgs()) {
+            options.collectFiles.push_back(Source{ arg });
+        }
+    } else if (options.phase == AaptOptions::Phase::Compile) {
+        for (const std::string& arg : flag::getArgs()) {
+            options.compileFiles.push_back(Source{ arg });
+        }
+    } else if (options.phase == AaptOptions::Phase::Link) {
+        for (const std::string& arg : flag::getArgs()) {
+            options.linkFiles.push_back(Source{ arg });
         }
-        ++argsIter;
     }
-    return true;
+    return options;
 }
 
-struct CollectValuesItem {
-    Source source;
-    ConfigDescription config;
-};
-
-bool collectValues(std::shared_ptr<ResourceTable> table, const CollectValuesItem& item) {
-    std::ifstream in(item.source.path, std::ifstream::binary);
+static bool collectValues(const std::shared_ptr<ResourceTable>& table, const Source& source,
+                          const ConfigDescription& config) {
+    std::ifstream in(source.path, std::ifstream::binary);
     if (!in) {
-        Logger::error(item.source) << strerror(errno) << std::endl;
+        Logger::error(source) << strerror(errno) << std::endl;
         return false;
     }
 
     std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
-    ResourceParser parser(table, item.source, item.config, xmlParser);
+    ResourceParser parser(table, source, config, xmlParser);
     return parser.parse();
 }
 
@@ -750,7 +664,7 @@ struct ResourcePathData {
  * Resource file paths are expected to look like:
  * [--/res/]type[-config]/name
  */
-Maybe<ResourcePathData> extractResourcePathData(const Source& source) {
+static Maybe<ResourcePathData> extractResourcePathData(const Source& source) {
     std::vector<std::string> parts = util::splitAndLowercase(source.path, '/');
     if (parts.size() < 2) {
         Logger::error(source) << "bad resource path." << std::endl;
@@ -792,16 +706,58 @@ Maybe<ResourcePathData> extractResourcePathData(const Source& source) {
     };
 }
 
-static bool doLegacy(std::shared_ptr<ResourceTable> table, std::shared_ptr<Resolver> resolver,
-                     const AaptOptions& options) {
+bool doAll(AaptOptions* options, const std::shared_ptr<ResourceTable>& table,
+           const std::shared_ptr<Resolver>& resolver) {
+    const bool versionStyles = (options->phase == AaptOptions::Phase::Full ||
+            options->phase == AaptOptions::Phase::Link);
+    const bool verifyNoMissingSymbols = (options->phase == AaptOptions::Phase::Full ||
+            options->phase == AaptOptions::Phase::Link);
+    const bool compileFiles = (options->phase == AaptOptions::Phase::Full ||
+            options->phase == AaptOptions::Phase::Compile);
+    const bool flattenTable = (options->phase == AaptOptions::Phase::Full ||
+            options->phase == AaptOptions::Phase::Collect ||
+            options->phase == AaptOptions::Phase::Link);
+    const bool useExtendedChunks = options->phase == AaptOptions::Phase::Collect;
+
+    // Build the output table path.
+    Source outputTable = options->output;
+    if (options->phase == AaptOptions::Phase::Full) {
+        appendPath(&outputTable.path, "resources.arsc");
+    }
+
     bool error = false;
-    std::queue<CompileXml> xmlCompileQueue;
+    std::queue<CompileItem> compileQueue;
+
+    // If source directories were specified, walk them looking for resource files.
+    if (!options->sourceDirs.empty()) {
+        const char* customIgnore = getenv("ANDROID_AAPT_IGNORE");
+        FileFilter fileFilter;
+        if (customIgnore && customIgnore[0]) {
+            fileFilter.setPattern(customIgnore);
+        } else {
+            fileFilter.setPattern(
+                    "!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~");
+        }
+
+        for (const Source& source : options->sourceDirs) {
+            if (!walkTree(source, fileFilter, &options->collectFiles)) {
+                return false;
+            }
+        }
+    }
+
+    // Load all binary resource tables.
+    for (const Source& source : options->linkFiles) {
+        error |= !loadBinaryResourceTable(table, source);
+    }
+
+    if (error) {
+        return false;
+    }
 
-    //
-    // Read values XML files and XML/PNG files.
+    // Collect all the resource files.
     // Need to parse the resource type/config/filename.
-    //
-    for (const Source& source : options.sources) {
+    for (const Source& source : options->collectFiles) {
         Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
         if (!maybePathData) {
             return false;
@@ -809,351 +765,154 @@ static bool doLegacy(std::shared_ptr<ResourceTable> table, std::shared_ptr<Resol
 
         const ResourcePathData& pathData = maybePathData.value();
         if (pathData.resourceDir == u"values") {
-            if (options.verbose) {
+            if (options->verbose) {
                 Logger::note(source) << "collecting values..." << std::endl;
             }
 
-            error |= !collectValues(table, CollectValuesItem{ source, pathData.config });
+            error |= !collectValues(table, source, pathData.config);
             continue;
         }
 
         const ResourceType* type = parseResourceType(pathData.resourceDir);
         if (!type) {
-            Logger::error(source)
-                    << "invalid resource type '"
-                    << pathData.resourceDir
-                    << "'."
-                    << std::endl;
+            Logger::error(source) << "invalid resource type '" << pathData.resourceDir << "'."
+                                  << std::endl;
             return false;
         }
 
         ResourceName resourceName = { table->getPackage(), *type, pathData.name };
-        if (pathData.extension == "xml") {
-            if (options.verbose) {
-                Logger::note(source) << "collecting XML..." << std::endl;
-            }
 
-            error |= !collectXml(table, source, resourceName, pathData.config);
-            xmlCompileQueue.push(CompileXml{
-                    source,
-                    resourceName,
-                    pathData.config
-            });
-        } else {
-            std::unique_ptr<FileReference> fileReference = makeFileReference(
-                    table->getValueStringPool(),
-                    util::utf16ToUtf8(pathData.name) + "." + pathData.extension,
-                    *type, pathData.config);
+        // Add the file name to the resource table.
+        std::unique_ptr<FileReference> fileReference = makeFileReference(
+                table->getValueStringPool(),
+                util::utf16ToUtf8(pathData.name) + "." + pathData.extension,
+                *type, pathData.config);
+        error |= !table->addResource(resourceName, pathData.config, source.line(0),
+                                     std::move(fileReference));
 
-            error |= !table->addResource(resourceName, pathData.config, source.line(0),
-                                         std::move(fileReference));
+        if (pathData.extension == "xml") {
+            error |= !collectXml(table, source, resourceName, pathData.config);
         }
+
+        compileQueue.push(
+                CompileItem{ source, resourceName, pathData.config, pathData.extension });
     }
 
     if (error) {
         return false;
     }
 
-    versionStylesForCompat(table);
+    // Version all styles referencing attributes outside of their specified SDK version.
+    if (versionStyles) {
+        versionStylesForCompat(table);
+    }
 
-    //
-    // Verify all references and data types.
-    //
+    // Verify that all references are valid.
     Linker linker(table, resolver);
     if (!linker.linkAndValidate()) {
-        Logger::error()
-                << "linking failed."
-                << std::endl;
         return false;
     }
 
-    const auto& unresolvedRefs = linker.getUnresolvedReferences();
-    if (!unresolvedRefs.empty()) {
-        for (const auto& entry : unresolvedRefs) {
-            for (const auto& source : entry.second) {
-                Logger::error(source)
-                        << "unresolved symbol '"
-                        << entry.first
-                        << "'."
-                        << std::endl;
+    // Verify that all symbols exist.
+    if (verifyNoMissingSymbols) {
+        const auto& unresolvedRefs = linker.getUnresolvedReferences();
+        if (!unresolvedRefs.empty()) {
+            for (const auto& entry : unresolvedRefs) {
+                for (const auto& source : entry.second) {
+                    Logger::error(source) << "unresolved symbol '" << entry.first << "'."
+                                          << std::endl;
+                }
             }
-        }
-        return false;
-    }
-
-    //
-    // Compile the XML files.
-    //
-    while (!xmlCompileQueue.empty()) {
-        const CompileXml& item = xmlCompileQueue.front();
-
-        // Create the output path from the resource name.
-        std::stringstream outputPath;
-        outputPath << item.name.type;
-        if (item.config != ConfigDescription{}) {
-            outputPath << "-" << item.config.toString();
-        }
-
-        Source outSource = options.output;
-        appendPath(&outSource.path, "res");
-        appendPath(&outSource.path, outputPath.str());
-
-        if (!mkdirs(outSource.path)) {
-            Logger::error(outSource) << strerror(errno) << std::endl;
             return false;
         }
-
-        appendPath(&outSource.path, util::utf16ToUtf8(item.name.entry) + ".xml");
-
-        if (options.verbose) {
-            Logger::note(outSource) << "compiling XML file." << std::endl;
-        }
-
-        error |= !compileXml(resolver, item, outSource, &xmlCompileQueue);
-        xmlCompileQueue.pop();
-    }
-
-    if (error) {
-        return false;
-    }
-
-    //
-    // Compile the AndroidManifest.xml file.
-    //
-    if (!compileAndroidManifest(resolver, options)) {
-        return false;
     }
 
-    //
-    // Generate the Java R class.
-    //
-    if (options.generateJavaClass) {
-        Source outPath = options.generateJavaClass.value();
-        if (options.verbose) {
-            Logger::note()
-                    << "writing symbols to "
-                    << outPath
-                    << "."
-                    << std::endl;
-        }
-
-        for (std::string& part : util::split(util::utf16ToUtf8(table->getPackage()), '.')) {
-            appendPath(&outPath.path, part);
-        }
-
-        if (!mkdirs(outPath.path)) {
-            Logger::error(outPath) << strerror(errno) << std::endl;
-            return false;
-        }
-
-        appendPath(&outPath.path, "R.java");
-
-        std::ofstream fout(outPath.path);
-        if (!fout) {
-            Logger::error(outPath) << strerror(errno) << std::endl;
-            return false;
-        }
-
-        JavaClassGenerator generator(table, JavaClassGenerator::Options{});
-        if (!generator.generate(fout)) {
-            Logger::error(outPath)
-                    << generator.getError()
-                    << "."
-                    << std::endl;
-            return false;
-        }
-    }
-
-    //
-    // Flatten resource table.
-    //
-    if (table->begin() != table->end()) {
-        BigBuffer buffer(1024);
-        TableFlattener::Options tableOptions;
-        tableOptions.useExtendedChunks = false;
-        TableFlattener flattener(tableOptions);
-        if (!flattener.flatten(&buffer, *table)) {
-            Logger::error()
-                    << "failed to flatten resource table->"
-                    << std::endl;
-            return false;
-        }
-
-        if (options.verbose) {
-            Logger::note()
-                    << "Final resource table size="
-                    << util::formatSize(buffer.size())
-                    << std::endl;
-        }
+    // Compile files.
+    if (compileFiles) {
+        // First process any input compile files.
+        for (const Source& source : options->compileFiles) {
+            Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
+            if (!maybePathData) {
+                return false;
+            }
 
-        std::string outTable(options.output.path);
-        appendPath(&outTable, "resources.arsc");
+            const ResourcePathData& pathData = maybePathData.value();
+            const ResourceType* type = parseResourceType(pathData.resourceDir);
+            if (!type) {
+                Logger::error(source) << "invalid resource type '" << pathData.resourceDir
+                                      << "'." << std::endl;
+                return false;
+            }
 
-        std::ofstream fout(outTable, std::ofstream::binary);
-        if (!fout) {
-            Logger::error(Source{outTable})
-                    << strerror(errno)
-                    << "."
-                    << std::endl;
-            return false;
+            ResourceName resourceName = { table->getPackage(), *type, pathData.name };
+            compileQueue.push(
+                    CompileItem{ source, resourceName, pathData.config, pathData.extension });
         }
 
-        if (!util::writeAll(fout, buffer)) {
-            Logger::error(Source{outTable})
-                    << strerror(errno)
-                    << "."
-                    << std::endl;
-            return false;
-        }
-        fout.flush();
-    }
-    return true;
-}
-
-static bool doCollect(std::shared_ptr<ResourceTable> table, std::shared_ptr<Resolver> resolver,
-                      const AaptOptions& options) {
-    bool error = false;
-
-    //
-    // Read values XML files and XML/PNG files.
-    // Need to parse the resource type/config/filename.
-    //
-    for (const Source& source : options.sources) {
-        Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
-        if (!maybePathData) {
-            return false;
-        }
+        // Now process the actual compile queue.
+        for (; !compileQueue.empty(); compileQueue.pop()) {
+            const CompileItem& item = compileQueue.front();
 
-        const ResourcePathData& pathData = maybePathData.value();
-        if (pathData.resourceDir == u"values") {
-            if (options.verbose) {
-                Logger::note(source) << "collecting values..." << std::endl;
+            // Create the output directory path from the resource type and config.
+            std::stringstream outputPath;
+            outputPath << item.name.type;
+            if (item.config != ConfigDescription{}) {
+                outputPath << "-" << item.config.toString();
             }
 
-            error |= !collectValues(table, CollectValuesItem{ source, pathData.config });
-            continue;
-        }
+            Source outSource = options->output;
+            appendPath(&outSource.path, "res");
+            appendPath(&outSource.path, outputPath.str());
 
-        const ResourceType* type = parseResourceType(pathData.resourceDir);
-        if (!type) {
-            Logger::error(source)
-                    << "invalid resource type '"
-                    << pathData.resourceDir
-                    << "'."
-                    << std::endl;
-            return false;
-        }
-
-        ResourceName resourceName = { table->getPackage(), *type, pathData.name };
-        if (pathData.extension == "xml") {
-            if (options.verbose) {
-                Logger::note(source) << "collecting XML..." << std::endl;
+            // Make the directory.
+            if (!mkdirs(outSource.path)) {
+                Logger::error(outSource) << strerror(errno) << std::endl;
+                return false;
             }
 
-            error |= !collectXml(table, source, resourceName, pathData.config);
-        } else {
-            std::unique_ptr<FileReference> fileReference = makeFileReference(
-                    table->getValueStringPool(),
-                    util::utf16ToUtf8(pathData.name) + "." + pathData.extension,
-                    *type,
-                    pathData.config);
-            error |= !table->addResource(resourceName, pathData.config, source.line(0),
-                                         std::move(fileReference));
-        }
-    }
-
-    if (error) {
-        return false;
-    }
+            // Add the file name to the directory path.
+            appendPath(&outSource.path, util::utf16ToUtf8(item.name.entry) + "." + item.extension);
 
-    Linker linker(table, resolver);
-    if (!linker.linkAndValidate()) {
-        return false;
-    }
+            if (item.extension == "xml") {
+                if (options->verbose) {
+                    Logger::note(outSource) << "compiling XML file." << std::endl;
+                }
 
-    //
-    // Flatten resource table->
-    //
-    if (table->begin() != table->end()) {
-        BigBuffer buffer(1024);
-        TableFlattener::Options tableOptions;
-        tableOptions.useExtendedChunks = true;
-        TableFlattener flattener(tableOptions);
-        if (!flattener.flatten(&buffer, *table)) {
-            Logger::error()
-                    << "failed to flatten resource table->"
-                    << std::endl;
-            return false;
-        }
+                error |= !compileXml(resolver, item, outSource, &compileQueue);
+            } else if (item.extension == "png" || item.extension == "9.png") {
+                if (options->verbose) {
+                    Logger::note(outSource) << "compiling png file." << std::endl;
+                }
 
-        std::ofstream fout(options.output.path, std::ofstream::binary);
-        if (!fout) {
-            Logger::error(options.output)
-                    << strerror(errno)
-                    << "."
-                    << std::endl;
-            return false;
+                error |= !compilePng(item.source, outSource);
+            } else {
+                error |= !copyFile(item.source, outSource);
+            }
         }
 
-        if (!util::writeAll(fout, buffer)) {
-            Logger::error(options.output)
-                    << strerror(errno)
-                    << "."
-                    << std::endl;
+        if (error) {
             return false;
         }
-        fout.flush();
-    }
-    return true;
-}
-
-static bool doLink(std::shared_ptr<ResourceTable> table, std::shared_ptr<Resolver> resolver,
-                   const AaptOptions& options) {
-    bool error = false;
-
-    for (const Source& source : options.sources) {
-        error |= !loadBinaryResourceTable(table, source);
-    }
-
-    if (error) {
-        return false;
     }
 
-    versionStylesForCompat(table);
-
-    Linker linker(table, resolver);
-    if (!linker.linkAndValidate()) {
-        return false;
-    }
-
-    const auto& unresolvedRefs = linker.getUnresolvedReferences();
-    if (!unresolvedRefs.empty()) {
-        for (const auto& entry : unresolvedRefs) {
-            for (const auto& source : entry.second) {
-                Logger::error(source)
-                        << "unresolved symbol '"
-                        << entry.first
-                        << "'."
-                        << std::endl;
-            }
+    // Compile and validate the AndroidManifest.xml.
+    if (!options->manifest.path.empty()) {
+        if (!compileAndroidManifest(resolver, *options)) {
+            return false;
         }
-        return false;
     }
 
-    //
-    // Generate the Java R class.
-    //
-    if (options.generateJavaClass) {
-        Source outPath = options.generateJavaClass.value();
-        if (options.verbose) {
-            Logger::note()
-                    << "writing symbols to "
-                    << outPath
-                    << "."
-                    << std::endl;
+    // Generate the Java class file.
+    if (options->generateJavaClass) {
+        Source outPath = options->generateJavaClass.value();
+        if (options->verbose) {
+            Logger::note() << "writing symbols to " << outPath << "." << std::endl;
         }
 
-        for (std::string& part : util::split(util::utf16ToUtf8(table->getPackage()), '.')) {
+        // Build the output directory from the package name.
+        // Eg. com.android.app -> com/android/app
+        const std::string packageUtf8 = util::utf16ToUtf8(table->getPackage());
+        for (StringPiece part : util::tokenize<char>(packageUtf8, '.')) {
             appendPath(&outPath.path, part);
         }
 
@@ -1170,52 +929,37 @@ static bool doLink(std::shared_ptr<ResourceTable> table, std::shared_ptr<Resolve
             return false;
         }
 
-        JavaClassGenerator generator(table, JavaClassGenerator::Options{});
+        JavaClassGenerator generator(table, {});
         if (!generator.generate(fout)) {
-            Logger::error(outPath)
-                    << generator.getError()
-                    << "."
-                    << std::endl;
+            Logger::error(outPath) << generator.getError() << "." << std::endl;
             return false;
         }
     }
 
-    //
-    // Flatten resource table.
-    //
-    if (table->begin() != table->end()) {
+    // Flatten the resource table.
+    if (flattenTable && table->begin() != table->end()) {
         BigBuffer buffer(1024);
         TableFlattener::Options tableOptions;
-        tableOptions.useExtendedChunks = false;
+        tableOptions.useExtendedChunks = useExtendedChunks;
         TableFlattener flattener(tableOptions);
         if (!flattener.flatten(&buffer, *table)) {
-            Logger::error()
-                    << "failed to flatten resource table->"
-                    << std::endl;
+            Logger::error() << "failed to flatten resource table." << std::endl;
             return false;
         }
 
-        if (options.verbose) {
-            Logger::note()
-                    << "Final resource table size="
-                    << util::formatSize(buffer.size())
-                    << std::endl;
+        if (options->verbose) {
+            Logger::note() << "Final resource table size=" << util::formatSize(buffer.size())
+                           << std::endl;
         }
 
-        std::ofstream fout(options.output.path, std::ofstream::binary);
+        std::ofstream fout(outputTable.path, std::ofstream::binary);
         if (!fout) {
-            Logger::error(options.output)
-                    << strerror(errno)
-                    << "."
-                    << std::endl;
+            Logger::error(outputTable) << strerror(errno) << "." << std::endl;
             return false;
         }
 
         if (!util::writeAll(fout, buffer)) {
-            Logger::error(options.output)
-                    << strerror(errno)
-                    << "."
-                    << std::endl;
+            Logger::error(outputTable) << strerror(errno) << "." << std::endl;
             return false;
         }
         fout.flush();
@@ -1223,134 +967,24 @@ static bool doLink(std::shared_ptr<ResourceTable> table, std::shared_ptr<Resolve
     return true;
 }
 
-static bool doCompile(std::shared_ptr<ResourceTable> table, std::shared_ptr<Resolver> resolver,
-                      const AaptOptions& options) {
-    std::queue<CompileXml> xmlCompileQueue;
-
-    for (const Source& source : options.sources) {
-        Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
-        if (!maybePathData) {
-            return false;
-        }
-
-        ResourcePathData& pathData = maybePathData.value();
-        const ResourceType* type = parseResourceType(pathData.resourceDir);
-        if (!type) {
-            Logger::error(source)
-                    << "invalid resource type '"
-                    << pathData.resourceDir
-                    << "'."
-                    << std::endl;
-            return false;
-        }
-
-        ResourceName resourceName = { table->getPackage(), *type, pathData.name };
-        if (pathData.extension == "xml") {
-            xmlCompileQueue.push(CompileXml{
-                    source,
-                    resourceName,
-                    pathData.config
-            });
-        } else {
-            // TODO(adamlesinski): Handle images here.
-        }
-    }
-
-    bool error = false;
-    while (!xmlCompileQueue.empty()) {
-        const CompileXml& item = xmlCompileQueue.front();
-
-        // Create the output path from the resource name.
-        std::stringstream outputPath;
-        outputPath << item.name.type;
-        if (item.config != ConfigDescription{}) {
-            outputPath << "-" << item.config.toString();
-        }
-
-        Source outSource = options.output;
-        appendPath(&outSource.path, "res");
-        appendPath(&outSource.path, outputPath.str());
-
-        if (!mkdirs(outSource.path)) {
-            Logger::error(outSource) << strerror(errno) << std::endl;
-            return false;
-        }
-
-        appendPath(&outSource.path, util::utf16ToUtf8(item.name.entry) + ".xml");
-
-        if (options.verbose) {
-            Logger::note(outSource) << "compiling XML file." << std::endl;
-        }
-
-        error |= !compileXml(resolver, item, outSource, &xmlCompileQueue);
-        xmlCompileQueue.pop();
-    }
-    return !error;
-}
-
 int main(int argc, char** argv) {
     Logger::setLog(std::make_shared<Log>(std::cerr, std::cerr));
+    AaptOptions options = prepareArgs(argc, argv);
 
-    std::vector<StringPiece> args;
-    args.reserve(argc - 1);
-    for (int i = 1; i < argc; i++) {
-        args.emplace_back(argv[i], strlen(argv[i]));
-    }
-
-    if (args.empty()) {
-        Logger::error() << "no command specified." << std::endl;
-        return 1;
-    }
-
-    AaptOptions options;
-
-    // Check the command we're running.
-    const StringPiece& command = args.front();
-    if (command == "package") {
-        if (!prepareLegacy(std::begin(args) + 1, std::end(args), options)) {
-            return 1;
-        }
-    } else if (command == "collect") {
-        if (!prepareCollect(std::begin(args) + 1, std::end(args), options)) {
-            return 1;
-        }
-    } else if (command == "link") {
-        if (!prepareLink(std::begin(args) + 1, std::end(args), options)) {
-            return 1;
-        }
-    } else if (command == "compile") {
-        if (!prepareCompile(std::begin(args) + 1, std::end(args), options)) {
-            return 1;
+    // If we specified a manifest, go ahead and load the package name from the manifest.
+    if (!options.manifest.path.empty()) {
+        if (!loadAppInfo(options.manifest, &options.appInfo)) {
+            return false;
         }
-    } else {
-        Logger::error() << "unknown command '" << command << "'." << std::endl;
-        return 1;
     }
 
-    //
     // Verify we have some common options set.
-    //
-
-    if (options.sources.empty()) {
-        Logger::error() << "no sources specified." << std::endl;
-        return false;
-    }
-
-    if (options.output.path.empty()) {
-        Logger::error() << "no output directory specified." << std::endl;
-        return false;
-    }
-
     if (options.appInfo.package.empty()) {
         Logger::error() << "no package name specified." << std::endl;
         return false;
     }
 
-
-    //
-    // Every phase needs a resource table and a resolver/linker.
-    //
-
+    // Every phase needs a resource table.
     std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
     table->setPackage(options.appInfo.package);
     if (options.appInfo.package == u"android") {
@@ -1359,9 +993,7 @@ int main(int argc, char** argv) {
         table->setPackageId(0x7f);
     }
 
-    //
     // Load the included libraries.
-    //
     std::shared_ptr<android::AssetManager> libraries = std::make_shared<android::AssetManager>();
     for (const Source& source : options.libraries) {
         if (util::stringEndsWith(source.path, ".arsc")) {
@@ -1393,33 +1025,9 @@ int main(int argc, char** argv) {
     // Make the resolver that will cache IDs for us.
     std::shared_ptr<Resolver> resolver = std::make_shared<Resolver>(table, libraries);
 
-    //
-    // Dispatch to the real phase here.
-    //
-
-    bool result = true;
-    switch (options.phase) {
-        case AaptOptions::Phase::LegacyFull:
-            result = doLegacy(table, resolver, options);
-            break;
-
-        case AaptOptions::Phase::Collect:
-            result = doCollect(table, resolver, options);
-            break;
-
-        case AaptOptions::Phase::Link:
-            result = doLink(table, resolver, options);
-            break;
-
-        case AaptOptions::Phase::Compile:
-            result = doCompile(table, resolver, options);
-            break;
-    }
-
-    if (!result) {
-        Logger::error()
-                << "aapt exiting with failures."
-                << std::endl;
+    // Do the work.
+    if (!doAll(&options, table, resolver)) {
+        Logger::error() << "aapt exiting with failures." << std::endl;
         return 1;
     }
     return 0;
diff --git a/tools/aapt2/Png.cpp b/tools/aapt2/Png.cpp
new file mode 100644 (file)
index 0000000..dd753f1
--- /dev/null
@@ -0,0 +1,1284 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Logger.h"
+#include "Png.h"
+#include "Source.h"
+#include "Util.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <iostream>
+#include <png.h>
+#include <sstream>
+#include <string>
+#include <vector>
+#include <zlib.h>
+
+namespace aapt {
+
+constexpr bool kDebug = false;
+constexpr size_t kPngSignatureSize = 8u;
+
+struct PngInfo {
+    ~PngInfo() {
+        for (png_bytep row : rows) {
+            if (row != nullptr) {
+                delete[] row;
+            }
+        }
+
+        delete[] xDivs;
+        delete[] yDivs;
+    }
+
+    void* serialize9Patch() {
+        void* serialized = android::Res_png_9patch::serialize(info9Patch, xDivs, yDivs,
+                                                              colors.data());
+        reinterpret_cast<android::Res_png_9patch*>(serialized)->deviceToFile();
+        return serialized;
+    }
+
+    uint32_t width = 0;
+    uint32_t height = 0;
+    std::vector<png_bytep> rows;
+
+    bool is9Patch = false;
+    android::Res_png_9patch info9Patch;
+    int32_t* xDivs = nullptr;
+    int32_t* yDivs = nullptr;
+    std::vector<uint32_t> colors;
+
+    // Layout padding.
+    bool haveLayoutBounds = false;
+    int32_t layoutBoundsLeft;
+    int32_t layoutBoundsTop;
+    int32_t layoutBoundsRight;
+    int32_t layoutBoundsBottom;
+
+    // Round rect outline description.
+    int32_t outlineInsetsLeft;
+    int32_t outlineInsetsTop;
+    int32_t outlineInsetsRight;
+    int32_t outlineInsetsBottom;
+    float outlineRadius;
+    uint8_t outlineAlpha;
+};
+
+static void readDataFromStream(png_structp readPtr, png_bytep data, png_size_t length) {
+    std::istream* input = reinterpret_cast<std::istream*>(png_get_io_ptr(readPtr));
+    if (!input->read(reinterpret_cast<char*>(data), length)) {
+        png_error(readPtr, strerror(errno));
+    }
+}
+
+static void writeDataToStream(png_structp writePtr, png_bytep data, png_size_t length) {
+    std::ostream* output = reinterpret_cast<std::ostream*>(png_get_io_ptr(writePtr));
+    if (!output->write(reinterpret_cast<const char*>(data), length)) {
+        png_error(writePtr, strerror(errno));
+    }
+}
+
+static void flushDataToStream(png_structp writePtr) {
+    std::ostream* output = reinterpret_cast<std::ostream*>(png_get_io_ptr(writePtr));
+    if (!output->flush()) {
+        png_error(writePtr, strerror(errno));
+    }
+}
+
+static void logWarning(png_structp readPtr, png_const_charp warningMessage) {
+    SourceLogger* logger = reinterpret_cast<SourceLogger*>(png_get_error_ptr(readPtr));
+    logger->warn() << warningMessage << "." << std::endl;
+}
+
+
+static bool readPng(png_structp readPtr, png_infop infoPtr, PngInfo* outInfo,
+                    std::string* outError) {
+    if (setjmp(png_jmpbuf(readPtr))) {
+        *outError = "failed reading png";
+        return false;
+    }
+
+    png_set_sig_bytes(readPtr, kPngSignatureSize);
+    png_read_info(readPtr, infoPtr);
+
+    int colorType, bitDepth, interlaceType, compressionType;
+    png_get_IHDR(readPtr, infoPtr, &outInfo->width, &outInfo->height, &bitDepth, &colorType,
+                 &interlaceType, &compressionType, nullptr);
+
+    if (colorType == PNG_COLOR_TYPE_PALETTE) {
+        png_set_palette_to_rgb(readPtr);
+    }
+
+    if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) {
+        png_set_expand_gray_1_2_4_to_8(readPtr);
+    }
+
+    if (png_get_valid(readPtr, infoPtr, PNG_INFO_tRNS)) {
+        png_set_tRNS_to_alpha(readPtr);
+    }
+
+    if (bitDepth == 16) {
+        png_set_strip_16(readPtr);
+    }
+
+    if (!(colorType & PNG_COLOR_MASK_ALPHA)) {
+        png_set_add_alpha(readPtr, 0xFF, PNG_FILLER_AFTER);
+    }
+
+    if (colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
+        png_set_gray_to_rgb(readPtr);
+    }
+
+    png_set_interlace_handling(readPtr);
+    png_read_update_info(readPtr, infoPtr);
+
+    const uint32_t rowBytes = png_get_rowbytes(readPtr, infoPtr);
+    outInfo->rows.resize(outInfo->height);
+    for (size_t i = 0; i < outInfo->height; i++) {
+        outInfo->rows[i] = new png_byte[rowBytes];
+    }
+
+    png_read_image(readPtr, outInfo->rows.data());
+    png_read_end(readPtr, infoPtr);
+    return true;
+}
+
+static void checkNinePatchSerialization(android::Res_png_9patch* inPatch,  void* data) {
+    size_t patchSize = inPatch->serializedSize();
+    void* newData = malloc(patchSize);
+    memcpy(newData, data, patchSize);
+    android::Res_png_9patch* outPatch = inPatch->deserialize(newData);
+    outPatch->fileToDevice();
+    // deserialization is done in place, so outPatch == newData
+    assert(outPatch == newData);
+    assert(outPatch->numXDivs == inPatch->numXDivs);
+    assert(outPatch->numYDivs == inPatch->numYDivs);
+    assert(outPatch->paddingLeft == inPatch->paddingLeft);
+    assert(outPatch->paddingRight == inPatch->paddingRight);
+    assert(outPatch->paddingTop == inPatch->paddingTop);
+    assert(outPatch->paddingBottom == inPatch->paddingBottom);
+/*    for (int i = 0; i < outPatch->numXDivs; i++) {
+        assert(outPatch->getXDivs()[i] == inPatch->getXDivs()[i]);
+    }
+    for (int i = 0; i < outPatch->numYDivs; i++) {
+        assert(outPatch->getYDivs()[i] == inPatch->getYDivs()[i]);
+    }
+    for (int i = 0; i < outPatch->numColors; i++) {
+        assert(outPatch->getColors()[i] == inPatch->getColors()[i]);
+    }*/
+    free(newData);
+}
+
+/*static void dump_image(int w, int h, const png_byte* const* rows, int color_type) {
+    int i, j, rr, gg, bb, aa;
+
+    int bpp;
+    if (color_type == PNG_COLOR_TYPE_PALETTE || color_type == PNG_COLOR_TYPE_GRAY) {
+        bpp = 1;
+    } else if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
+        bpp = 2;
+    } else if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGB_ALPHA) {
+        // We use a padding byte even when there is no alpha
+        bpp = 4;
+    } else {
+        printf("Unknown color type %d.\n", color_type);
+    }
+
+    for (j = 0; j < h; j++) {
+        const png_byte* row = rows[j];
+        for (i = 0; i < w; i++) {
+            rr = row[0];
+            gg = row[1];
+            bb = row[2];
+            aa = row[3];
+            row += bpp;
+
+            if (i == 0) {
+                printf("Row %d:", j);
+            }
+            switch (bpp) {
+            case 1:
+                printf(" (%d)", rr);
+                break;
+            case 2:
+                printf(" (%d %d", rr, gg);
+                break;
+            case 3:
+                printf(" (%d %d %d)", rr, gg, bb);
+                break;
+            case 4:
+                printf(" (%d %d %d %d)", rr, gg, bb, aa);
+                break;
+            }
+            if (i == (w - 1)) {
+                printf("\n");
+            }
+        }
+    }
+}*/
+
+#define MAX(a,b) ((a)>(b)?(a):(b))
+#define ABS(a)   ((a)<0?-(a):(a))
+
+static void analyze_image(SourceLogger* logger, const PngInfo& imageInfo, int grayscaleTolerance,
+                          png_colorp rgbPalette, png_bytep alphaPalette,
+                          int *paletteEntries, bool *hasTransparency, int *colorType,
+                          png_bytepp outRows) {
+    int w = imageInfo.width;
+    int h = imageInfo.height;
+    int i, j, rr, gg, bb, aa, idx;
+    uint32_t colors[256], col;
+    int num_colors = 0;
+    int maxGrayDeviation = 0;
+
+    bool isOpaque = true;
+    bool isPalette = true;
+    bool isGrayscale = true;
+
+    // Scan the entire image and determine if:
+    // 1. Every pixel has R == G == B (grayscale)
+    // 2. Every pixel has A == 255 (opaque)
+    // 3. There are no more than 256 distinct RGBA colors
+
+    if (kDebug) {
+        printf("Initial image data:\n");
+        //dump_image(w, h, imageInfo.rows.data(), PNG_COLOR_TYPE_RGB_ALPHA);
+    }
+
+    for (j = 0; j < h; j++) {
+        const png_byte* row = imageInfo.rows[j];
+        png_bytep out = outRows[j];
+        for (i = 0; i < w; i++) {
+            rr = *row++;
+            gg = *row++;
+            bb = *row++;
+            aa = *row++;
+
+            int odev = maxGrayDeviation;
+            maxGrayDeviation = MAX(ABS(rr - gg), maxGrayDeviation);
+            maxGrayDeviation = MAX(ABS(gg - bb), maxGrayDeviation);
+            maxGrayDeviation = MAX(ABS(bb - rr), maxGrayDeviation);
+            if (maxGrayDeviation > odev) {
+                if (kDebug) {
+                    printf("New max dev. = %d at pixel (%d, %d) = (%d %d %d %d)\n",
+                            maxGrayDeviation, i, j, rr, gg, bb, aa);
+                }
+            }
+
+            // Check if image is really grayscale
+            if (isGrayscale) {
+                if (rr != gg || rr != bb) {
+                    if (kDebug) {
+                        printf("Found a non-gray pixel at %d, %d = (%d %d %d %d)\n",
+                                i, j, rr, gg, bb, aa);
+                    }
+                    isGrayscale = false;
+                }
+            }
+
+            // Check if image is really opaque
+            if (isOpaque) {
+                if (aa != 0xff) {
+                    if (kDebug) {
+                        printf("Found a non-opaque pixel at %d, %d = (%d %d %d %d)\n",
+                                i, j, rr, gg, bb, aa);
+                    }
+                    isOpaque = false;
+                }
+            }
+
+            // Check if image is really <= 256 colors
+            if (isPalette) {
+                col = (uint32_t) ((rr << 24) | (gg << 16) | (bb << 8) | aa);
+                bool match = false;
+                for (idx = 0; idx < num_colors; idx++) {
+                    if (colors[idx] == col) {
+                        match = true;
+                        break;
+                    }
+                }
+
+                // Write the palette index for the pixel to outRows optimistically
+                // We might overwrite it later if we decide to encode as gray or
+                // gray + alpha
+                *out++ = idx;
+                if (!match) {
+                    if (num_colors == 256) {
+                        if (kDebug) {
+                            printf("Found 257th color at %d, %d\n", i, j);
+                        }
+                        isPalette = false;
+                    } else {
+                        colors[num_colors++] = col;
+                    }
+                }
+            }
+        }
+    }
+
+    *paletteEntries = 0;
+    *hasTransparency = !isOpaque;
+    int bpp = isOpaque ? 3 : 4;
+    int paletteSize = w * h + bpp * num_colors;
+
+    if (kDebug) {
+        printf("isGrayscale = %s\n", isGrayscale ? "true" : "false");
+        printf("isOpaque = %s\n", isOpaque ? "true" : "false");
+        printf("isPalette = %s\n", isPalette ? "true" : "false");
+        printf("Size w/ palette = %d, gray+alpha = %d, rgb(a) = %d\n",
+                paletteSize, 2 * w * h, bpp * w * h);
+        printf("Max gray deviation = %d, tolerance = %d\n", maxGrayDeviation, grayscaleTolerance);
+    }
+
+    // Choose the best color type for the image.
+    // 1. Opaque gray - use COLOR_TYPE_GRAY at 1 byte/pixel
+    // 2. Gray + alpha - use COLOR_TYPE_PALETTE if the number of distinct combinations
+    //     is sufficiently small, otherwise use COLOR_TYPE_GRAY_ALPHA
+    // 3. RGB(A) - use COLOR_TYPE_PALETTE if the number of distinct colors is sufficiently
+    //     small, otherwise use COLOR_TYPE_RGB{_ALPHA}
+    if (isGrayscale) {
+        if (isOpaque) {
+            *colorType = PNG_COLOR_TYPE_GRAY; // 1 byte/pixel
+        } else {
+            // Use a simple heuristic to determine whether using a palette will
+            // save space versus using gray + alpha for each pixel.
+            // This doesn't take into account chunk overhead, filtering, LZ
+            // compression, etc.
+            if (isPalette && (paletteSize < 2 * w * h)) {
+                *colorType = PNG_COLOR_TYPE_PALETTE; // 1 byte/pixel + 4 bytes/color
+            } else {
+                *colorType = PNG_COLOR_TYPE_GRAY_ALPHA; // 2 bytes per pixel
+            }
+        }
+    } else if (isPalette && (paletteSize < bpp * w * h)) {
+        *colorType = PNG_COLOR_TYPE_PALETTE;
+    } else {
+        if (maxGrayDeviation <= grayscaleTolerance) {
+            logger->note() << "forcing image to gray (max deviation = " << maxGrayDeviation
+                           << ")."
+                           << std::endl;
+            *colorType = isOpaque ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_GRAY_ALPHA;
+        } else {
+            *colorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
+        }
+    }
+
+    // Perform postprocessing of the image or palette data based on the final
+    // color type chosen
+
+    if (*colorType == PNG_COLOR_TYPE_PALETTE) {
+        // Create separate RGB and Alpha palettes and set the number of colors
+        *paletteEntries = num_colors;
+
+        // Create the RGB and alpha palettes
+        for (int idx = 0; idx < num_colors; idx++) {
+            col = colors[idx];
+            rgbPalette[idx].red   = (png_byte) ((col >> 24) & 0xff);
+            rgbPalette[idx].green = (png_byte) ((col >> 16) & 0xff);
+            rgbPalette[idx].blue  = (png_byte) ((col >>  8) & 0xff);
+            alphaPalette[idx]     = (png_byte)  (col        & 0xff);
+        }
+    } else if (*colorType == PNG_COLOR_TYPE_GRAY || *colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
+        // If the image is gray or gray + alpha, compact the pixels into outRows
+        for (j = 0; j < h; j++) {
+            const png_byte* row = imageInfo.rows[j];
+            png_bytep out = outRows[j];
+            for (i = 0; i < w; i++) {
+                rr = *row++;
+                gg = *row++;
+                bb = *row++;
+                aa = *row++;
+
+                if (isGrayscale) {
+                    *out++ = rr;
+                } else {
+                    *out++ = (png_byte) (rr * 0.2126f + gg * 0.7152f + bb * 0.0722f);
+                }
+                if (!isOpaque) {
+                    *out++ = aa;
+                }
+           }
+        }
+    }
+}
+
+static bool writePng(png_structp writePtr, png_infop infoPtr, PngInfo* info,
+                     int grayScaleTolerance, SourceLogger* logger, std::string* outError) {
+    if (setjmp(png_jmpbuf(writePtr))) {
+        *outError = "failed to write png";
+        return false;
+    }
+
+    uint32_t width, height;
+    int colorType, bitDepth, interlaceType, compressionType;
+
+    png_unknown_chunk unknowns[3];
+    unknowns[0].data = nullptr;
+    unknowns[1].data = nullptr;
+    unknowns[2].data = nullptr;
+
+    png_bytepp outRows = (png_bytepp) malloc((int) info->height * sizeof(png_bytep));
+    if (outRows == (png_bytepp) 0) {
+        printf("Can't allocate output buffer!\n");
+        exit(1);
+    }
+    for (uint32_t i = 0; i < info->height; i++) {
+        outRows[i] = (png_bytep) malloc(2 * (int) info->width);
+        if (outRows[i] == (png_bytep) 0) {
+            printf("Can't allocate output buffer!\n");
+            exit(1);
+        }
+    }
+
+    png_set_compression_level(writePtr, Z_BEST_COMPRESSION);
+
+    if (kDebug) {
+        logger->note() << "writing image: w = " << info->width
+                       << ", h = " << info->height
+                       << std::endl;
+    }
+
+    png_color rgbPalette[256];
+    png_byte alphaPalette[256];
+    bool hasTransparency;
+    int paletteEntries;
+
+    analyze_image(logger, *info, grayScaleTolerance, rgbPalette, alphaPalette,
+                  &paletteEntries, &hasTransparency, &colorType, outRows);
+
+    // If the image is a 9-patch, we need to preserve it as a ARGB file to make
+    // sure the pixels will not be pre-dithered/clamped until we decide they are
+    if (info->is9Patch && (colorType == PNG_COLOR_TYPE_RGB ||
+            colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_PALETTE)) {
+        colorType = PNG_COLOR_TYPE_RGB_ALPHA;
+    }
+
+    if (kDebug) {
+        switch (colorType) {
+        case PNG_COLOR_TYPE_PALETTE:
+            logger->note() << "has " << paletteEntries
+                           << " colors" << (hasTransparency ? " (with alpha)" : "")
+                           << ", using PNG_COLOR_TYPE_PALLETTE."
+                           << std::endl;
+            break;
+        case PNG_COLOR_TYPE_GRAY:
+            logger->note() << "is opaque gray, using PNG_COLOR_TYPE_GRAY." << std::endl;
+            break;
+        case PNG_COLOR_TYPE_GRAY_ALPHA:
+            logger->note() << "is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA." << std::endl;
+            break;
+        case PNG_COLOR_TYPE_RGB:
+            logger->note() << "is opaque RGB, using PNG_COLOR_TYPE_RGB." << std::endl;
+            break;
+        case PNG_COLOR_TYPE_RGB_ALPHA:
+            logger->note() << "is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA." << std::endl;
+            break;
+        }
+    }
+
+    png_set_IHDR(writePtr, infoPtr, info->width, info->height, 8, colorType,
+                 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
+
+    if (colorType == PNG_COLOR_TYPE_PALETTE) {
+        png_set_PLTE(writePtr, infoPtr, rgbPalette, paletteEntries);
+        if (hasTransparency) {
+            png_set_tRNS(writePtr, infoPtr, alphaPalette, paletteEntries, (png_color_16p) 0);
+        }
+        png_set_filter(writePtr, 0, PNG_NO_FILTERS);
+    } else {
+        png_set_filter(writePtr, 0, PNG_ALL_FILTERS);
+    }
+
+    if (info->is9Patch) {
+        int chunkCount = 2 + (info->haveLayoutBounds ? 1 : 0);
+        int pIndex = info->haveLayoutBounds ? 2 : 1;
+        int bIndex = 1;
+        int oIndex = 0;
+
+        // Chunks ordered thusly because older platforms depend on the base 9 patch data being last
+        png_bytep chunkNames = info->haveLayoutBounds
+                ? (png_bytep)"npOl\0npLb\0npTc\0"
+                : (png_bytep)"npOl\0npTc";
+
+        // base 9 patch data
+        if (kDebug) {
+            logger->note() << "adding 9-patch info..." << std::endl;
+        }
+        strcpy((char*)unknowns[pIndex].name, "npTc");
+        unknowns[pIndex].data = (png_byte*) info->serialize9Patch();
+        unknowns[pIndex].size = info->info9Patch.serializedSize();
+        // TODO: remove the check below when everything works
+        checkNinePatchSerialization(&info->info9Patch, unknowns[pIndex].data);
+
+        // automatically generated 9 patch outline data
+        int chunkSize = sizeof(png_uint_32) * 6;
+        strcpy((char*)unknowns[oIndex].name, "npOl");
+        unknowns[oIndex].data = (png_byte*) calloc(chunkSize, 1);
+        png_byte outputData[chunkSize];
+        memcpy(&outputData, &info->outlineInsetsLeft, 4 * sizeof(png_uint_32));
+        ((float*) outputData)[4] = info->outlineRadius;
+        ((png_uint_32*) outputData)[5] = info->outlineAlpha;
+        memcpy(unknowns[oIndex].data, &outputData, chunkSize);
+        unknowns[oIndex].size = chunkSize;
+
+        // optional optical inset / layout bounds data
+        if (info->haveLayoutBounds) {
+            int chunkSize = sizeof(png_uint_32) * 4;
+            strcpy((char*)unknowns[bIndex].name, "npLb");
+            unknowns[bIndex].data = (png_byte*) calloc(chunkSize, 1);
+            memcpy(unknowns[bIndex].data, &info->layoutBoundsLeft, chunkSize);
+            unknowns[bIndex].size = chunkSize;
+        }
+
+        for (int i = 0; i < chunkCount; i++) {
+            unknowns[i].location = PNG_HAVE_PLTE;
+        }
+        png_set_keep_unknown_chunks(writePtr, PNG_HANDLE_CHUNK_ALWAYS,
+                                    chunkNames, chunkCount);
+        png_set_unknown_chunks(writePtr, infoPtr, unknowns, chunkCount);
+
+#if PNG_LIBPNG_VER < 10600
+        // Deal with unknown chunk location bug in 1.5.x and earlier.
+        png_set_unknown_chunk_location(writePtr, infoPtr, 0, PNG_HAVE_PLTE);
+        if (info->haveLayoutBounds) {
+            png_set_unknown_chunk_location(writePtr, infoPtr, 1, PNG_HAVE_PLTE);
+        }
+#endif
+    }
+
+    png_write_info(writePtr, infoPtr);
+
+    png_bytepp rows;
+    if (colorType == PNG_COLOR_TYPE_RGB || colorType == PNG_COLOR_TYPE_RGB_ALPHA) {
+        if (colorType == PNG_COLOR_TYPE_RGB) {
+            png_set_filler(writePtr, 0, PNG_FILLER_AFTER);
+        }
+        rows = info->rows.data();
+    } else {
+        rows = outRows;
+    }
+    png_write_image(writePtr, rows);
+
+    if (kDebug) {
+        printf("Final image data:\n");
+        //dump_image(info->width, info->height, rows, colorType);
+    }
+
+    png_write_end(writePtr, infoPtr);
+
+    for (uint32_t i = 0; i < info->height; i++) {
+        free(outRows[i]);
+    }
+    free(outRows);
+    free(unknowns[0].data);
+    free(unknowns[1].data);
+    free(unknowns[2].data);
+
+    png_get_IHDR(writePtr, infoPtr, &width, &height, &bitDepth, &colorType, &interlaceType,
+                 &compressionType, nullptr);
+
+    if (kDebug) {
+        logger->note() << "image written: w = " << width << ", h = " << height
+                       << ", d = " << bitDepth << ", colors = " << colorType
+                       << ", inter = " << interlaceType << ", comp = " << compressionType
+                       << std::endl;
+    }
+    return true;
+}
+
+constexpr uint32_t kColorWhite = 0xffffffffu;
+constexpr uint32_t kColorTick = 0xff000000u;
+constexpr uint32_t kColorLayoutBoundsTick = 0xff0000ffu;
+
+enum class TickType {
+    kNone,
+    kTick,
+    kLayoutBounds,
+    kBoth
+};
+
+static TickType tickType(png_bytep p, bool transparent, const char** outError) {
+    png_uint_32 color = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
+
+    if (transparent) {
+        if (p[3] == 0) {
+            return TickType::kNone;
+        }
+        if (color == kColorLayoutBoundsTick) {
+            return TickType::kLayoutBounds;
+        }
+        if (color == kColorTick) {
+            return TickType::kTick;
+        }
+
+        // Error cases
+        if (p[3] != 0xff) {
+            *outError = "Frame pixels must be either solid or transparent "
+                        "(not intermediate alphas)";
+            return TickType::kNone;
+        }
+
+        if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
+            *outError = "Ticks in transparent frame must be black or red";
+        }
+        return TickType::kTick;
+    }
+
+    if (p[3] != 0xFF) {
+        *outError = "White frame must be a solid color (no alpha)";
+    }
+    if (color == kColorWhite) {
+        return TickType::kNone;
+    }
+    if (color == kColorTick) {
+        return TickType::kTick;
+    }
+    if (color == kColorLayoutBoundsTick) {
+        return TickType::kLayoutBounds;
+    }
+
+    if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
+        *outError = "Ticks in white frame must be black or red";
+        return TickType::kNone;
+    }
+    return TickType::kTick;
+}
+
+enum class TickState {
+    kStart,
+    kInside1,
+    kOutside1
+};
+
+static bool getHorizontalTicks(png_bytep row, int width, bool transparent, bool required,
+                               int32_t* outLeft, int32_t* outRight, const char** outError,
+                               uint8_t* outDivs, bool multipleAllowed) {
+    *outLeft = *outRight = -1;
+    TickState state = TickState::kStart;
+    bool found = false;
+
+    for (int i = 1; i < width - 1; i++) {
+        if (tickType(row+i*4, transparent, outError) == TickType::kTick) {
+            if (state == TickState::kStart ||
+                (state == TickState::kOutside1 && multipleAllowed)) {
+                *outLeft = i-1;
+                *outRight = width-2;
+                found = true;
+                if (outDivs != NULL) {
+                    *outDivs += 2;
+                }
+                state = TickState::kInside1;
+            } else if (state == TickState::kOutside1) {
+                *outError = "Can't have more than one marked region along edge";
+                *outLeft = i;
+                return false;
+            }
+        } else if (!*outError) {
+            if (state == TickState::kInside1) {
+                // We're done with this div.  Move on to the next.
+                *outRight = i-1;
+                outRight += 2;
+                outLeft += 2;
+                state = TickState::kOutside1;
+            }
+        } else {
+            *outLeft = i;
+            return false;
+        }
+    }
+
+    if (required && !found) {
+        *outError = "No marked region found along edge";
+        *outLeft = -1;
+        return false;
+    }
+    return true;
+}
+
+static bool getVerticalTicks(png_bytepp rows, int offset, int height, bool transparent,
+                             bool required, int32_t* outTop, int32_t* outBottom,
+                             const char** outError, uint8_t* outDivs, bool multipleAllowed) {
+    *outTop = *outBottom = -1;
+    TickState state = TickState::kStart;
+    bool found = false;
+
+    for (int i = 1; i < height - 1; i++) {
+        if (tickType(rows[i]+offset, transparent, outError) == TickType::kTick) {
+            if (state == TickState::kStart ||
+                (state == TickState::kOutside1 && multipleAllowed)) {
+                *outTop = i-1;
+                *outBottom = height-2;
+                found = true;
+                if (outDivs != NULL) {
+                    *outDivs += 2;
+                }
+                state = TickState::kInside1;
+            } else if (state == TickState::kOutside1) {
+                *outError = "Can't have more than one marked region along edge";
+                *outTop = i;
+                return false;
+            }
+        } else if (!*outError) {
+            if (state == TickState::kInside1) {
+                // We're done with this div.  Move on to the next.
+                *outBottom = i-1;
+                outTop += 2;
+                outBottom += 2;
+                state = TickState::kOutside1;
+            }
+        } else {
+            *outTop = i;
+            return false;
+        }
+    }
+
+    if (required && !found) {
+        *outError = "No marked region found along edge";
+        *outTop = -1;
+        return false;
+    }
+    return true;
+}
+
+static bool getHorizontalLayoutBoundsTicks(png_bytep row, int width, bool transparent,
+                                           bool /* required */, int32_t* outLeft,
+                                           int32_t* outRight, const char** outError) {
+    *outLeft = *outRight = 0;
+
+    // Look for left tick
+    if (tickType(row + 4, transparent, outError) == TickType::kLayoutBounds) {
+        // Starting with a layout padding tick
+        int i = 1;
+        while (i < width - 1) {
+            (*outLeft)++;
+            i++;
+            if (tickType(row + i * 4, transparent, outError) != TickType::kLayoutBounds) {
+                break;
+            }
+        }
+    }
+
+    // Look for right tick
+    if (tickType(row + (width - 2) * 4, transparent, outError) == TickType::kLayoutBounds) {
+        // Ending with a layout padding tick
+        int i = width - 2;
+        while (i > 1) {
+            (*outRight)++;
+            i--;
+            if (tickType(row+i*4, transparent, outError) != TickType::kLayoutBounds) {
+                break;
+            }
+        }
+    }
+    return true;
+}
+
+static bool getVerticalLayoutBoundsTicks(png_bytepp rows, int offset, int height, bool transparent,
+                                         bool /* required */, int32_t* outTop, int32_t* outBottom,
+                                         const char** outError) {
+    *outTop = *outBottom = 0;
+
+    // Look for top tick
+    if (tickType(rows[1] + offset, transparent, outError) == TickType::kLayoutBounds) {
+        // Starting with a layout padding tick
+        int i = 1;
+        while (i < height - 1) {
+            (*outTop)++;
+            i++;
+            if (tickType(rows[i] + offset, transparent, outError) != TickType::kLayoutBounds) {
+                break;
+            }
+        }
+    }
+
+    // Look for bottom tick
+    if (tickType(rows[height - 2] + offset, transparent, outError) == TickType::kLayoutBounds) {
+        // Ending with a layout padding tick
+        int i = height - 2;
+        while (i > 1) {
+            (*outBottom)++;
+            i--;
+            if (tickType(rows[i] + offset, transparent, outError) != TickType::kLayoutBounds) {
+                break;
+            }
+        }
+    }
+    return true;
+}
+
+static void findMaxOpacity(png_bytepp rows, int startX, int startY, int endX, int endY,
+                           int dX, int dY, int* outInset) {
+    uint8_t maxOpacity = 0;
+    int inset = 0;
+    *outInset = 0;
+    for (int x = startX, y = startY; x != endX && y != endY; x += dX, y += dY, inset++) {
+        png_byte* color = rows[y] + x * 4;
+        uint8_t opacity = color[3];
+        if (opacity > maxOpacity) {
+            maxOpacity = opacity;
+            *outInset = inset;
+        }
+        if (opacity == 0xff) return;
+    }
+}
+
+static uint8_t maxAlphaOverRow(png_bytep row, int startX, int endX) {
+    uint8_t maxAlpha = 0;
+    for (int x = startX; x < endX; x++) {
+        uint8_t alpha = (row + x * 4)[3];
+        if (alpha > maxAlpha) maxAlpha = alpha;
+    }
+    return maxAlpha;
+}
+
+static uint8_t maxAlphaOverCol(png_bytepp rows, int offsetX, int startY, int endY) {
+    uint8_t maxAlpha = 0;
+    for (int y = startY; y < endY; y++) {
+        uint8_t alpha = (rows[y] + offsetX * 4)[3];
+        if (alpha > maxAlpha) maxAlpha = alpha;
+    }
+    return maxAlpha;
+}
+
+static void getOutline(PngInfo* image) {
+    int midX = image->width / 2;
+    int midY = image->height / 2;
+    int endX = image->width - 2;
+    int endY = image->height - 2;
+
+    // find left and right extent of nine patch content on center row
+    if (image->width > 4) {
+        findMaxOpacity(image->rows.data(), 1, midY, midX, -1, 1, 0, &image->outlineInsetsLeft);
+        findMaxOpacity(image->rows.data(), endX, midY, midX, -1, -1, 0,
+                       &image->outlineInsetsRight);
+    } else {
+        image->outlineInsetsLeft = 0;
+        image->outlineInsetsRight = 0;
+    }
+
+    // find top and bottom extent of nine patch content on center column
+    if (image->height > 4) {
+        findMaxOpacity(image->rows.data(), midX, 1, -1, midY, 0, 1, &image->outlineInsetsTop);
+        findMaxOpacity(image->rows.data(), midX, endY, -1, midY, 0, -1,
+                       &image->outlineInsetsBottom);
+    } else {
+        image->outlineInsetsTop = 0;
+        image->outlineInsetsBottom = 0;
+    }
+
+    int innerStartX = 1 + image->outlineInsetsLeft;
+    int innerStartY = 1 + image->outlineInsetsTop;
+    int innerEndX = endX - image->outlineInsetsRight;
+    int innerEndY = endY - image->outlineInsetsBottom;
+    int innerMidX = (innerEndX + innerStartX) / 2;
+    int innerMidY = (innerEndY + innerStartY) / 2;
+
+    // assuming the image is a round rect, compute the radius by marching
+    // diagonally from the top left corner towards the center
+    image->outlineAlpha = std::max(
+            maxAlphaOverRow(image->rows[innerMidY], innerStartX, innerEndX),
+            maxAlphaOverCol(image->rows.data(), innerMidX, innerStartY, innerStartY));
+
+    int diagonalInset = 0;
+    findMaxOpacity(image->rows.data(), innerStartX, innerStartY, innerMidX, innerMidY, 1, 1,
+                   &diagonalInset);
+
+    /* Determine source radius based upon inset:
+     *     sqrt(r^2 + r^2) = sqrt(i^2 + i^2) + r
+     *     sqrt(2) * r = sqrt(2) * i + r
+     *     (sqrt(2) - 1) * r = sqrt(2) * i
+     *     r = sqrt(2) / (sqrt(2) - 1) * i
+     */
+    image->outlineRadius = 3.4142f * diagonalInset;
+
+    if (kDebug) {
+        printf("outline insets %d %d %d %d, rad %f, alpha %x\n",
+                image->outlineInsetsLeft,
+                image->outlineInsetsTop,
+                image->outlineInsetsRight,
+                image->outlineInsetsBottom,
+                image->outlineRadius,
+                image->outlineAlpha);
+    }
+}
+
+static uint32_t getColor(png_bytepp rows, int left, int top, int right, int bottom) {
+    png_bytep color = rows[top] + left*4;
+
+    if (left > right || top > bottom) {
+        return android::Res_png_9patch::TRANSPARENT_COLOR;
+    }
+
+    while (top <= bottom) {
+        for (int i = left; i <= right; i++) {
+            png_bytep p = rows[top]+i*4;
+            if (color[3] == 0) {
+                if (p[3] != 0) {
+                    return android::Res_png_9patch::NO_COLOR;
+                }
+            } else if (p[0] != color[0] || p[1] != color[1] ||
+                    p[2] != color[2] || p[3] != color[3]) {
+                return android::Res_png_9patch::NO_COLOR;
+            }
+        }
+        top++;
+    }
+
+    if (color[3] == 0) {
+        return android::Res_png_9patch::TRANSPARENT_COLOR;
+    }
+    return (color[3]<<24) | (color[0]<<16) | (color[1]<<8) | color[2];
+}
+
+static bool do9Patch(PngInfo* image, std::string* outError) {
+    image->is9Patch = true;
+
+    int W = image->width;
+    int H = image->height;
+    int i, j;
+
+    const int maxSizeXDivs = W * sizeof(int32_t);
+    const int maxSizeYDivs = H * sizeof(int32_t);
+    int32_t* xDivs = image->xDivs = new int32_t[W];
+    int32_t* yDivs = image->yDivs = new int32_t[H];
+    uint8_t numXDivs = 0;
+    uint8_t numYDivs = 0;
+
+    int8_t numColors;
+    int numRows;
+    int numCols;
+    int top;
+    int left;
+    int right;
+    int bottom;
+    memset(xDivs, -1, maxSizeXDivs);
+    memset(yDivs, -1, maxSizeYDivs);
+    image->info9Patch.paddingLeft = image->info9Patch.paddingRight = -1;
+    image->info9Patch.paddingTop = image->info9Patch.paddingBottom = -1;
+    image->layoutBoundsLeft = image->layoutBoundsRight = 0;
+    image->layoutBoundsTop = image->layoutBoundsBottom = 0;
+
+    png_bytep p = image->rows[0];
+    bool transparent = p[3] == 0;
+    bool hasColor = false;
+
+    const char* errorMsg = nullptr;
+    int errorPixel = -1;
+    const char* errorEdge = nullptr;
+
+    int colorIndex = 0;
+    std::vector<png_bytep> newRows;
+
+    // Validate size...
+    if (W < 3 || H < 3) {
+        errorMsg = "Image must be at least 3x3 (1x1 without frame) pixels";
+        goto getout;
+    }
+
+    // Validate frame...
+    if (!transparent &&
+            (p[0] != 0xFF || p[1] != 0xFF || p[2] != 0xFF || p[3] != 0xFF)) {
+        errorMsg = "Must have one-pixel frame that is either transparent or white";
+        goto getout;
+    }
+
+    // Find left and right of sizing areas...
+    if (!getHorizontalTicks(p, W, transparent, true, &xDivs[0], &xDivs[1], &errorMsg, &numXDivs,
+                            true)) {
+        errorPixel = xDivs[0];
+        errorEdge = "top";
+        goto getout;
+    }
+
+    // Find top and bottom of sizing areas...
+    if (!getVerticalTicks(image->rows.data(), 0, H, transparent, true, &yDivs[0], &yDivs[1],
+                          &errorMsg, &numYDivs, true)) {
+        errorPixel = yDivs[0];
+        errorEdge = "left";
+        goto getout;
+    }
+
+    // Copy patch size data into image...
+    image->info9Patch.numXDivs = numXDivs;
+    image->info9Patch.numYDivs = numYDivs;
+
+    // Find left and right of padding area...
+    if (!getHorizontalTicks(image->rows[H-1], W, transparent, false,
+                            &image->info9Patch.paddingLeft, &image->info9Patch.paddingRight,
+                            &errorMsg, nullptr, false)) {
+        errorPixel = image->info9Patch.paddingLeft;
+        errorEdge = "bottom";
+        goto getout;
+    }
+
+    // Find top and bottom of padding area...
+    if (!getVerticalTicks(image->rows.data(), (W-1)*4, H, transparent, false,
+                          &image->info9Patch.paddingTop, &image->info9Patch.paddingBottom,
+                          &errorMsg, nullptr, false)) {
+        errorPixel = image->info9Patch.paddingTop;
+        errorEdge = "right";
+        goto getout;
+    }
+
+    // Find left and right of layout padding...
+    getHorizontalLayoutBoundsTicks(image->rows[H-1], W, transparent, false,
+                                   &image->layoutBoundsLeft, &image->layoutBoundsRight, &errorMsg);
+
+    getVerticalLayoutBoundsTicks(image->rows.data(), (W-1)*4, H, transparent, false,
+                                 &image->layoutBoundsTop, &image->layoutBoundsBottom, &errorMsg);
+
+    image->haveLayoutBounds = image->layoutBoundsLeft != 0
+                               || image->layoutBoundsRight != 0
+                               || image->layoutBoundsTop != 0
+                               || image->layoutBoundsBottom != 0;
+
+    if (image->haveLayoutBounds) {
+        if (kDebug) {
+            printf("layoutBounds=%d %d %d %d\n", image->layoutBoundsLeft, image->layoutBoundsTop,
+                    image->layoutBoundsRight, image->layoutBoundsBottom);
+        }
+    }
+
+    // use opacity of pixels to estimate the round rect outline
+    getOutline(image);
+
+    // If padding is not yet specified, take values from size.
+    if (image->info9Patch.paddingLeft < 0) {
+        image->info9Patch.paddingLeft = xDivs[0];
+        image->info9Patch.paddingRight = W - 2 - xDivs[1];
+    } else {
+        // Adjust value to be correct!
+        image->info9Patch.paddingRight = W - 2 - image->info9Patch.paddingRight;
+    }
+    if (image->info9Patch.paddingTop < 0) {
+        image->info9Patch.paddingTop = yDivs[0];
+        image->info9Patch.paddingBottom = H - 2 - yDivs[1];
+    } else {
+        // Adjust value to be correct!
+        image->info9Patch.paddingBottom = H - 2 - image->info9Patch.paddingBottom;
+    }
+
+/*    if (kDebug) {
+        printf("Size ticks for %s: x0=%d, x1=%d, y0=%d, y1=%d\n", imageName,
+                xDivs[0], xDivs[1],
+                yDivs[0], yDivs[1]);
+        printf("padding ticks for %s: l=%d, r=%d, t=%d, b=%d\n", imageName,
+                image->info9Patch.paddingLeft, image->info9Patch.paddingRight,
+                image->info9Patch.paddingTop, image->info9Patch.paddingBottom);
+    }*/
+
+    // Remove frame from image.
+    newRows.resize(H - 2);
+    for (i = 0; i < H - 2; i++) {
+        newRows[i] = image->rows[i + 1];
+        memmove(newRows[i], newRows[i] + 4, (W - 2) * 4);
+    }
+    image->rows.swap(newRows);
+
+    image->width -= 2;
+    W = image->width;
+    image->height -= 2;
+    H = image->height;
+
+    // Figure out the number of rows and columns in the N-patch
+    numCols = numXDivs + 1;
+    if (xDivs[0] == 0) {  // Column 1 is strechable
+        numCols--;
+    }
+    if (xDivs[numXDivs - 1] == W) {
+        numCols--;
+    }
+    numRows = numYDivs + 1;
+    if (yDivs[0] == 0) {  // Row 1 is strechable
+        numRows--;
+    }
+    if (yDivs[numYDivs - 1] == H) {
+        numRows--;
+    }
+
+    // Make sure the amount of rows and columns will fit in the number of
+    // colors we can use in the 9-patch format.
+    if (numRows * numCols > 0x7F) {
+        errorMsg = "Too many rows and columns in 9-patch perimeter";
+        goto getout;
+    }
+
+    numColors = numRows * numCols;
+    image->info9Patch.numColors = numColors;
+    image->colors.resize(numColors);
+
+    // Fill in color information for each patch.
+
+    uint32_t c;
+    top = 0;
+
+    // The first row always starts with the top being at y=0 and the bottom
+    // being either yDivs[1] (if yDivs[0]=0) of yDivs[0].  In the former case
+    // the first row is stretchable along the Y axis, otherwise it is fixed.
+    // The last row always ends with the bottom being bitmap.height and the top
+    // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or
+    // yDivs[numYDivs-1]. In the former case the last row is stretchable along
+    // the Y axis, otherwise it is fixed.
+    //
+    // The first and last columns are similarly treated with respect to the X
+    // axis.
+    //
+    // The above is to help explain some of the special casing that goes on the
+    // code below.
+
+    // The initial yDiv and whether the first row is considered stretchable or
+    // not depends on whether yDiv[0] was zero or not.
+    for (j = (yDivs[0] == 0 ? 1 : 0); j <= numYDivs && top < H; j++) {
+        if (j == numYDivs) {
+            bottom = H;
+        } else {
+            bottom = yDivs[j];
+        }
+        left = 0;
+        // The initial xDiv and whether the first column is considered
+        // stretchable or not depends on whether xDiv[0] was zero or not.
+        for (i = xDivs[0] == 0 ? 1 : 0; i <= numXDivs && left < W; i++) {
+            if (i == numXDivs) {
+                right = W;
+            } else {
+                right = xDivs[i];
+            }
+            c = getColor(image->rows.data(), left, top, right - 1, bottom - 1);
+            image->colors[colorIndex++] = c;
+            if (kDebug) {
+                if (c != android::Res_png_9patch::NO_COLOR) {
+                    hasColor = true;
+                }
+            }
+            left = right;
+        }
+        top = bottom;
+    }
+
+    assert(colorIndex == numColors);
+
+    if (kDebug && hasColor) {
+        for (i = 0; i < numColors; i++) {
+            if (i == 0) printf("Colors:\n");
+            printf(" #%08x", image->colors[i]);
+            if (i == numColors - 1) printf("\n");
+        }
+    }
+getout:
+    if (errorMsg) {
+        std::stringstream err;
+        err << "9-patch malformed: " << errorMsg;
+        if (!errorEdge) {
+            err << "." << std::endl;
+            if (errorPixel >= 0) {
+                err << "Found at pixel #" << errorPixel << " along " << errorEdge << " edge";
+            } else {
+                err << "Found along " << errorEdge << " edge";
+            }
+        }
+        *outError = err.str();
+        return false;
+    }
+    return true;
+}
+
+
+bool Png::process(const Source& source, std::istream& input, std::ostream& output,
+                  const Options& options, std::string* outError) {
+    png_byte signature[kPngSignatureSize];
+
+    // Read the PNG signature first.
+    if (!input.read(reinterpret_cast<char*>(signature), kPngSignatureSize)) {
+        *outError = strerror(errno);
+        return false;
+    }
+
+    // If the PNG signature doesn't match, bail early.
+    if (png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
+        *outError = "not a valid png file";
+        return false;
+    }
+
+    SourceLogger logger(source);
+    bool result = false;
+    png_structp readPtr = nullptr;
+    png_infop infoPtr = nullptr;
+    png_structp writePtr = nullptr;
+    png_infop writeInfoPtr = nullptr;
+    PngInfo pngInfo = {};
+
+    readPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr);
+    if (!readPtr) {
+        *outError = "failed to allocate read ptr";
+        goto bail;
+    }
+
+    infoPtr = png_create_info_struct(readPtr);
+    if (!infoPtr) {
+        *outError = "failed to allocate info ptr";
+        goto bail;
+    }
+
+    png_set_error_fn(readPtr, reinterpret_cast<png_voidp>(&logger), nullptr, logWarning);
+
+    // Set the read function to read from std::istream.
+    png_set_read_fn(readPtr, (png_voidp)&input, readDataFromStream);
+
+    if (!readPng(readPtr, infoPtr, &pngInfo, outError)) {
+        goto bail;
+    }
+
+    if (util::stringEndsWith(source.path, ".9.png")) {
+        if (!do9Patch(&pngInfo, outError)) {
+            goto bail;
+        }
+    }
+
+    writePtr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr);
+    if (!writePtr) {
+        *outError = "failed to allocate write ptr";
+        goto bail;
+    }
+
+    writeInfoPtr = png_create_info_struct(writePtr);
+    if (!writeInfoPtr) {
+        *outError = "failed to allocate write info ptr";
+        goto bail;
+    }
+
+    png_set_error_fn(writePtr, nullptr, nullptr, logWarning);
+
+    // Set the write function to write to std::ostream.
+    png_set_write_fn(writePtr, (png_voidp)&output, writeDataToStream, flushDataToStream);
+
+    if (!writePng(writePtr, writeInfoPtr, &pngInfo, options.grayScaleTolerance, &logger,
+                  outError)) {
+        goto bail;
+    }
+
+    result = true;
+bail:
+    if (readPtr) {
+        png_destroy_read_struct(&readPtr, &infoPtr, nullptr);
+    }
+
+    if (writePtr) {
+        png_destroy_write_struct(&writePtr, &writeInfoPtr);
+    }
+    return result;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/Png.h b/tools/aapt2/Png.h
new file mode 100644 (file)
index 0000000..bc80754
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_PNG_H
+#define AAPT_PNG_H
+
+#include "Source.h"
+
+#include <iostream>
+#include <string>
+
+namespace aapt {
+
+struct Png {
+    struct Options {
+        int grayScaleTolerance = 0;
+    };
+
+    bool process(const Source& source, std::istream& input, std::ostream& output,
+                 const Options& options, std::string* outError);
+};
+
+} // namespace aapt
+
+#endif // AAPT_PNG_H
diff --git a/tools/aapt2/data/res/drawable/icon.png b/tools/aapt2/data/res/drawable/icon.png
new file mode 100644 (file)
index 0000000..4bff9b9
Binary files /dev/null and b/tools/aapt2/data/res/drawable/icon.png differ
diff --git a/tools/aapt2/data/res/drawable/test.9.png b/tools/aapt2/data/res/drawable/test.9.png
new file mode 100644 (file)
index 0000000..33daa11
Binary files /dev/null and b/tools/aapt2/data/res/drawable/test.9.png differ