summaryrefslogtreecommitdiffstats
path: root/tools/aapt2
diff options
context:
space:
mode:
authorAdam Lesinski <adamlesinski@google.com>2015-04-06 11:46:52 -0700
committerAdam Lesinski <adamlesinski@google.com>2015-04-09 17:19:06 -0700
commit98aa3ad6e46e3b0270785c8b3f9798e37e8af140 (patch)
tree268c3dbfbf8fb92e2e13610700e475f565c760ad /tools/aapt2
parent0dfd7fba38d0ae5172903ada322e76ed99002008 (diff)
downloadframeworks_base-98aa3ad6e46e3b0270785c8b3f9798e37e8af140.zip
frameworks_base-98aa3ad6e46e3b0270785c8b3f9798e37e8af140.tar.gz
frameworks_base-98aa3ad6e46e3b0270785c8b3f9798e37e8af140.tar.bz2
Add PNG and 9-patch support
Change-Id: I9ecdfdf82b82d59084490da518e167e256afd5f2
Diffstat (limited to 'tools/aapt2')
-rw-r--r--tools/aapt2/Android.mk10
-rw-r--r--tools/aapt2/Flag.cpp109
-rw-r--r--tools/aapt2/Flag.h28
-rw-r--r--tools/aapt2/Main.cpp1042
-rw-r--r--tools/aapt2/Png.cpp1284
-rw-r--r--tools/aapt2/Png.h38
-rw-r--r--tools/aapt2/data/res/drawable/icon.pngbin0 -> 2341 bytes
-rw-r--r--tools/aapt2/data/res/drawable/test.9.pngbin0 -> 124 bytes
8 files changed, 1792 insertions, 719 deletions
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index 9cea176..14f558e 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -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
index 0000000..b1ee8e7
--- /dev/null
+++ b/tools/aapt2/Flag.cpp
@@ -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
index 0000000..32f5f2c
--- /dev/null
+++ b/tools/aapt2/Flag.h
@@ -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
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index cfc5874..3a4b444 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -18,10 +18,12 @@
#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
index 0000000..dd753f1
--- /dev/null
+++ b/tools/aapt2/Png.cpp
@@ -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
index 0000000..bc80754
--- /dev/null
+++ b/tools/aapt2/Png.h
@@ -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
index 0000000..4bff9b9
--- /dev/null
+++ b/tools/aapt2/data/res/drawable/icon.png
Binary files 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
index 0000000..33daa11
--- /dev/null
+++ b/tools/aapt2/data/res/drawable/test.9.png
Binary files differ