Revert^2 "Move some image/9patch code to androidfw"

This reverts commit 917043bc2586743afda5a21386893fa8c787800b.

Reason for revert: Roll forward with fix

Test: Automatic
Bug: 296324826
Change-Id: I42a0b48c02fd497b2174c0c65f300265202f7ab1
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index 47a7f35..2f28363 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -63,15 +63,21 @@
         "AssetsProvider.cpp",
         "AttributeResolution.cpp",
         "BigBuffer.cpp",
+        "BigBufferStream.cpp",
         "ChunkIterator.cpp",
         "ConfigDescription.cpp",
+        "FileStream.cpp",
         "Idmap.cpp",
         "LoadedArsc.cpp",
         "Locale.cpp",
         "LocaleData.cpp",
         "misc.cpp",
+        "NinePatch.cpp",
         "ObbFile.cpp",
         "PosixUtils.cpp",
+        "Png.cpp",
+        "PngChunkFilter.cpp",
+        "PngCrunch.cpp",
         "ResourceTimer.cpp",
         "ResourceTypes.cpp",
         "ResourceUtils.cpp",
@@ -84,7 +90,10 @@
     ],
     export_include_dirs: ["include"],
     export_shared_lib_headers: ["libz"],
-    static_libs: ["libincfs-utils"],
+    static_libs: [
+        "libincfs-utils",
+        "libpng",
+    ],
     whole_static_libs: [
         "libandroidfw_pathutils",
         "libincfs-utils",
@@ -198,9 +207,11 @@
         "tests/ConfigDescription_test.cpp",
         "tests/ConfigLocale_test.cpp",
         "tests/DynamicRefTable_test.cpp",
+        "tests/FileStream_test.cpp",
         "tests/Idmap_test.cpp",
         "tests/LoadedArsc_test.cpp",
         "tests/Locale_test.cpp",
+        "tests/NinePatch_test.cpp",
         "tests/ResourceTimer_test.cpp",
         "tests/ResourceUtils_test.cpp",
         "tests/ResTable_test.cpp",
diff --git a/libs/androidfw/BigBufferStream.cpp b/libs/androidfw/BigBufferStream.cpp
new file mode 100644
index 0000000..f18199c
--- /dev/null
+++ b/libs/androidfw/BigBufferStream.cpp
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2017 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 "androidfw/BigBufferStream.h"
+
+#include <algorithm>
+
+namespace android {
+
+//
+// BigBufferInputStream
+//
+
+bool BigBufferInputStream::Next(const void** data, size_t* size) {
+  if (iter_ == buffer_->end()) {
+    return false;
+  }
+
+  if (offset_ == iter_->size) {
+    ++iter_;
+    if (iter_ == buffer_->end()) {
+      return false;
+    }
+    offset_ = 0;
+  }
+
+  *data = iter_->buffer.get() + offset_;
+  *size = iter_->size - offset_;
+  bytes_read_ += iter_->size - offset_;
+  offset_ = iter_->size;
+  return true;
+}
+
+void BigBufferInputStream::BackUp(size_t count) {
+  if (count > offset_) {
+    bytes_read_ -= offset_;
+    offset_ = 0;
+  } else {
+    offset_ -= count;
+    bytes_read_ -= count;
+  }
+}
+
+bool BigBufferInputStream::CanRewind() const {
+  return true;
+}
+
+bool BigBufferInputStream::Rewind() {
+  iter_ = buffer_->begin();
+  offset_ = 0;
+  bytes_read_ = 0;
+  return true;
+}
+
+size_t BigBufferInputStream::ByteCount() const {
+  return bytes_read_;
+}
+
+bool BigBufferInputStream::HadError() const {
+  return false;
+}
+
+size_t BigBufferInputStream::TotalSize() const {
+  return buffer_->size();
+}
+
+bool BigBufferInputStream::ReadFullyAtOffset(void* data, size_t byte_count, off64_t offset) {
+  if (byte_count == 0) {
+    return true;
+  }
+  if (offset < 0) {
+    return false;
+  }
+  if (offset > std::numeric_limits<off64_t>::max() - byte_count) {
+    return false;
+  }
+  if (offset + byte_count > buffer_->size()) {
+    return false;
+  }
+  auto p = reinterpret_cast<uint8_t*>(data);
+  for (auto iter = buffer_->begin(); iter != buffer_->end() && byte_count > 0; ++iter) {
+    if (offset < iter->size) {
+      size_t to_read = std::min(byte_count, (size_t)(iter->size - offset));
+      memcpy(p, iter->buffer.get() + offset, to_read);
+      byte_count -= to_read;
+      p += to_read;
+      offset = 0;
+    } else {
+      offset -= iter->size;
+    }
+  }
+  return byte_count == 0;
+}
+
+//
+// BigBufferOutputStream
+//
+
+bool BigBufferOutputStream::Next(void** data, size_t* size) {
+  *data = buffer_->NextBlock(size);
+  return true;
+}
+
+void BigBufferOutputStream::BackUp(size_t count) {
+  buffer_->BackUp(count);
+}
+
+size_t BigBufferOutputStream::ByteCount() const {
+  return buffer_->size();
+}
+
+bool BigBufferOutputStream::HadError() const {
+  return false;
+}
+
+}  // namespace android
diff --git a/libs/androidfw/FileStream.cpp b/libs/androidfw/FileStream.cpp
new file mode 100644
index 0000000..b86c9cb
--- /dev/null
+++ b/libs/androidfw/FileStream.cpp
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2017 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 "androidfw/FileStream.h"
+
+#include <errno.h>   // for errno
+#include <fcntl.h>   // for O_RDONLY
+#include <unistd.h>  // for read
+
+#include "android-base/errors.h"
+#include "android-base/file.h"  // for O_BINARY
+#include "android-base/macros.h"
+#include "android-base/utf8.h"
+
+#if defined(_WIN32)
+// This is only needed for O_CLOEXEC.
+#include <windows.h>
+#define O_CLOEXEC O_NOINHERIT
+#endif
+
+using ::android::base::SystemErrorCodeToString;
+using ::android::base::unique_fd;
+
+namespace android {
+
+FileInputStream::FileInputStream(const std::string& path, size_t buffer_capacity)
+    : buffer_capacity_(buffer_capacity) {
+  int mode = O_RDONLY | O_CLOEXEC | O_BINARY;
+  fd_.reset(TEMP_FAILURE_RETRY(::android::base::utf8::open(path.c_str(), mode)));
+  if (fd_ == -1) {
+    error_ = SystemErrorCodeToString(errno);
+  } else {
+    buffer_.reset(new uint8_t[buffer_capacity_]);
+  }
+}
+
+FileInputStream::FileInputStream(int fd, size_t buffer_capacity)
+    : fd_(fd), buffer_capacity_(buffer_capacity) {
+  if (fd_ < 0) {
+    error_ = "Bad File Descriptor";
+  } else {
+    buffer_.reset(new uint8_t[buffer_capacity_]);
+  }
+}
+
+bool FileInputStream::Next(const void** data, size_t* size) {
+  if (HadError()) {
+    return false;
+  }
+
+  // Deal with any remaining bytes after BackUp was called.
+  if (buffer_offset_ != buffer_size_) {
+    *data = buffer_.get() + buffer_offset_;
+    *size = buffer_size_ - buffer_offset_;
+    total_byte_count_ += buffer_size_ - buffer_offset_;
+    buffer_offset_ = buffer_size_;
+    return true;
+  }
+
+  ssize_t n = TEMP_FAILURE_RETRY(read(fd_, buffer_.get(), buffer_capacity_));
+  if (n < 0) {
+    error_ = SystemErrorCodeToString(errno);
+    fd_.reset();
+    buffer_.reset();
+    return false;
+  }
+
+  buffer_size_ = static_cast<size_t>(n);
+  buffer_offset_ = buffer_size_;
+  total_byte_count_ += buffer_size_;
+
+  *data = buffer_.get();
+  *size = buffer_size_;
+  return buffer_size_ != 0u;
+}
+
+void FileInputStream::BackUp(size_t count) {
+  if (count > buffer_offset_) {
+    count = buffer_offset_;
+  }
+  buffer_offset_ -= count;
+  total_byte_count_ -= count;
+}
+
+size_t FileInputStream::ByteCount() const {
+  return total_byte_count_;
+}
+
+bool FileInputStream::HadError() const {
+  return fd_ == -1;
+}
+
+std::string FileInputStream::GetError() const {
+  return error_;
+}
+
+bool FileInputStream::ReadFullyAtOffset(void* data, size_t byte_count, off64_t offset) {
+  return base::ReadFullyAtOffset(fd_, data, byte_count, offset);
+}
+
+FileOutputStream::FileOutputStream(const std::string& path, size_t buffer_capacity)
+    : buffer_capacity_(buffer_capacity) {
+  int mode = O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_BINARY;
+  owned_fd_.reset(TEMP_FAILURE_RETRY(::android::base::utf8::open(path.c_str(), mode, 0666)));
+  fd_ = owned_fd_.get();
+  if (fd_ < 0) {
+    error_ = SystemErrorCodeToString(errno);
+  } else {
+    buffer_.reset(new uint8_t[buffer_capacity_]);
+  }
+}
+
+FileOutputStream::FileOutputStream(unique_fd fd, size_t buffer_capacity)
+    : FileOutputStream(fd.get(), buffer_capacity) {
+  owned_fd_ = std::move(fd);
+}
+
+FileOutputStream::FileOutputStream(int fd, size_t buffer_capacity)
+    : fd_(fd), buffer_capacity_(buffer_capacity) {
+  if (fd_ < 0) {
+    error_ = "Bad File Descriptor";
+  } else {
+    buffer_.reset(new uint8_t[buffer_capacity_]);
+  }
+}
+
+FileOutputStream::~FileOutputStream() {
+  // Flush the buffer.
+  Flush();
+}
+
+bool FileOutputStream::Next(void** data, size_t* size) {
+  if (HadError()) {
+    return false;
+  }
+
+  if (buffer_offset_ == buffer_capacity_) {
+    if (!FlushImpl()) {
+      return false;
+    }
+  }
+
+  const size_t buffer_size = buffer_capacity_ - buffer_offset_;
+  *data = buffer_.get() + buffer_offset_;
+  *size = buffer_size;
+  total_byte_count_ += buffer_size;
+  buffer_offset_ = buffer_capacity_;
+  return true;
+}
+
+void FileOutputStream::BackUp(size_t count) {
+  if (count > buffer_offset_) {
+    count = buffer_offset_;
+  }
+  buffer_offset_ -= count;
+  total_byte_count_ -= count;
+}
+
+size_t FileOutputStream::ByteCount() const {
+  return total_byte_count_;
+}
+
+bool FileOutputStream::Flush() {
+  if (!HadError()) {
+    return FlushImpl();
+  }
+  return false;
+}
+
+bool FileOutputStream::FlushImpl() {
+  ssize_t n = TEMP_FAILURE_RETRY(write(fd_, buffer_.get(), buffer_offset_));
+  if (n < 0) {
+    error_ = SystemErrorCodeToString(errno);
+    owned_fd_.reset();
+    fd_ = -1;
+    buffer_.reset();
+    return false;
+  }
+
+  buffer_offset_ = 0u;
+  return true;
+}
+
+bool FileOutputStream::HadError() const {
+  return fd_ == -1;
+}
+
+std::string FileOutputStream::GetError() const {
+  return error_;
+}
+
+}  // namespace android
diff --git a/libs/androidfw/NinePatch.cpp b/libs/androidfw/NinePatch.cpp
new file mode 100644
index 0000000..1fdbebf
--- /dev/null
+++ b/libs/androidfw/NinePatch.cpp
@@ -0,0 +1,682 @@
+/*
+ * Copyright (C) 2016 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 <sstream>
+#include <string>
+#include <vector>
+
+#include "androidfw/Image.h"
+#include "androidfw/ResourceTypes.h"
+#include "androidfw/StringPiece.h"
+
+using android::StringPiece;
+
+namespace android {
+
+// Colors in the format 0xAARRGGBB (the way 9-patch expects it).
+constexpr static const uint32_t kColorOpaqueWhite = 0xffffffffu;
+constexpr static const uint32_t kColorOpaqueBlack = 0xff000000u;
+constexpr static const uint32_t kColorOpaqueRed = 0xffff0000u;
+
+constexpr static const uint32_t kPrimaryColor = kColorOpaqueBlack;
+constexpr static const uint32_t kSecondaryColor = kColorOpaqueRed;
+
+/**
+ * Returns the alpha value encoded in the 0xAARRGBB encoded pixel.
+ */
+static uint32_t get_alpha(uint32_t color);
+
+/**
+ * Determines whether a color on an ImageLine is valid.
+ * A 9patch image may use a transparent color as neutral,
+ * or a fully opaque white color as neutral, based on the
+ * pixel color at (0,0) of the image. One or the other is fine,
+ * but we need to ensure consistency throughout the image.
+ */
+class ColorValidator {
+ public:
+  virtual ~ColorValidator() = default;
+
+  /**
+   * Returns true if the color specified is a neutral color
+   * (no padding, stretching, or optical bounds).
+   */
+  virtual bool IsNeutralColor(uint32_t color) const = 0;
+
+  /**
+   * Returns true if the color is either a neutral color
+   * or one denoting padding, stretching, or optical bounds.
+   */
+  bool IsValidColor(uint32_t color) const {
+    switch (color) {
+      case kPrimaryColor:
+      case kSecondaryColor:
+        return true;
+    }
+    return IsNeutralColor(color);
+  }
+};
+
+// Walks an ImageLine and records Ranges of primary and secondary colors.
+// The primary color is black and is used to denote a padding or stretching
+// range,
+// depending on which border we're iterating over.
+// The secondary color is red and is used to denote optical bounds.
+//
+// An ImageLine is a templated-interface that would look something like this if
+// it
+// were polymorphic:
+//
+// class ImageLine {
+// public:
+//      virtual int32_t GetLength() const = 0;
+//      virtual uint32_t GetColor(int32_t idx) const = 0;
+// };
+//
+template <typename ImageLine>
+static bool FillRanges(const ImageLine* image_line, const ColorValidator* color_validator,
+                       std::vector<Range>* primary_ranges, std::vector<Range>* secondary_ranges,
+                       std::string* out_err) {
+  const int32_t length = image_line->GetLength();
+
+  uint32_t last_color = 0xffffffffu;
+  for (int32_t idx = 1; idx < length - 1; idx++) {
+    const uint32_t color = image_line->GetColor(idx);
+    if (!color_validator->IsValidColor(color)) {
+      *out_err = "found an invalid color";
+      return false;
+    }
+
+    if (color != last_color) {
+      // We are ending a range. Which range?
+      // note: encode the x offset without the final 1 pixel border.
+      if (last_color == kPrimaryColor) {
+        primary_ranges->back().end = idx - 1;
+      } else if (last_color == kSecondaryColor) {
+        secondary_ranges->back().end = idx - 1;
+      }
+
+      // We are starting a range. Which range?
+      // note: encode the x offset without the final 1 pixel border.
+      if (color == kPrimaryColor) {
+        primary_ranges->push_back(Range(idx - 1, length - 2));
+      } else if (color == kSecondaryColor) {
+        secondary_ranges->push_back(Range(idx - 1, length - 2));
+      }
+      last_color = color;
+    }
+  }
+  return true;
+}
+
+/**
+ * Iterates over a row in an image. Implements the templated ImageLine
+ * interface.
+ */
+class HorizontalImageLine {
+ public:
+  explicit HorizontalImageLine(uint8_t** rows, int32_t xoffset, int32_t yoffset, int32_t length)
+      : rows_(rows), xoffset_(xoffset), yoffset_(yoffset), length_(length) {
+  }
+
+  inline int32_t GetLength() const {
+    return length_;
+  }
+
+  inline uint32_t GetColor(int32_t idx) const {
+    return NinePatch::PackRGBA(rows_[yoffset_] + (idx + xoffset_) * 4);
+  }
+
+ private:
+  uint8_t** rows_;
+  int32_t xoffset_, yoffset_, length_;
+
+  DISALLOW_COPY_AND_ASSIGN(HorizontalImageLine);
+};
+
+/**
+ * Iterates over a column in an image. Implements the templated ImageLine
+ * interface.
+ */
+class VerticalImageLine {
+ public:
+  explicit VerticalImageLine(uint8_t** rows, int32_t xoffset, int32_t yoffset, int32_t length)
+      : rows_(rows), xoffset_(xoffset), yoffset_(yoffset), length_(length) {
+  }
+
+  inline int32_t GetLength() const {
+    return length_;
+  }
+
+  inline uint32_t GetColor(int32_t idx) const {
+    return NinePatch::PackRGBA(rows_[yoffset_ + idx] + (xoffset_ * 4));
+  }
+
+ private:
+  uint8_t** rows_;
+  int32_t xoffset_, yoffset_, length_;
+
+  DISALLOW_COPY_AND_ASSIGN(VerticalImageLine);
+};
+
+class DiagonalImageLine {
+ public:
+  explicit DiagonalImageLine(uint8_t** rows, int32_t xoffset, int32_t yoffset, int32_t xstep,
+                             int32_t ystep, int32_t length)
+      : rows_(rows),
+        xoffset_(xoffset),
+        yoffset_(yoffset),
+        xstep_(xstep),
+        ystep_(ystep),
+        length_(length) {
+  }
+
+  inline int32_t GetLength() const {
+    return length_;
+  }
+
+  inline uint32_t GetColor(int32_t idx) const {
+    return NinePatch::PackRGBA(rows_[yoffset_ + (idx * ystep_)] + ((idx + xoffset_) * xstep_) * 4);
+  }
+
+ private:
+  uint8_t** rows_;
+  int32_t xoffset_, yoffset_, xstep_, ystep_, length_;
+
+  DISALLOW_COPY_AND_ASSIGN(DiagonalImageLine);
+};
+
+class TransparentNeutralColorValidator : public ColorValidator {
+ public:
+  bool IsNeutralColor(uint32_t color) const override {
+    return get_alpha(color) == 0;
+  }
+};
+
+class WhiteNeutralColorValidator : public ColorValidator {
+ public:
+  bool IsNeutralColor(uint32_t color) const override {
+    return color == kColorOpaqueWhite;
+  }
+};
+
+inline static uint32_t get_alpha(uint32_t color) {
+  return (color & 0xff000000u) >> 24;
+}
+
+static bool PopulateBounds(const std::vector<Range>& padding,
+                           const std::vector<Range>& layout_bounds,
+                           const std::vector<Range>& stretch_regions, const int32_t length,
+                           int32_t* padding_start, int32_t* padding_end, int32_t* layout_start,
+                           int32_t* layout_end, StringPiece edge_name, std::string* out_err) {
+  if (padding.size() > 1) {
+    std::stringstream err_stream;
+    err_stream << "too many padding sections on " << edge_name << " border";
+    *out_err = err_stream.str();
+    return false;
+  }
+
+  *padding_start = 0;
+  *padding_end = 0;
+  if (!padding.empty()) {
+    const Range& range = padding.front();
+    *padding_start = range.start;
+    *padding_end = length - range.end;
+  } else if (!stretch_regions.empty()) {
+    // No padding was defined. Compute the padding from the first and last
+    // stretch regions.
+    *padding_start = stretch_regions.front().start;
+    *padding_end = length - stretch_regions.back().end;
+  }
+
+  if (layout_bounds.size() > 2) {
+    std::stringstream err_stream;
+    err_stream << "too many layout bounds sections on " << edge_name << " border";
+    *out_err = err_stream.str();
+    return false;
+  }
+
+  *layout_start = 0;
+  *layout_end = 0;
+  if (layout_bounds.size() >= 1) {
+    const Range& range = layout_bounds.front();
+    // If there is only one layout bound segment, it might not start at 0, but
+    // then it should
+    // end at length.
+    if (range.start != 0 && range.end != length) {
+      std::stringstream err_stream;
+      err_stream << "layout bounds on " << edge_name << " border must start at edge";
+      *out_err = err_stream.str();
+      return false;
+    }
+    *layout_start = range.end;
+
+    if (layout_bounds.size() >= 2) {
+      const Range& range = layout_bounds.back();
+      if (range.end != length) {
+        std::stringstream err_stream;
+        err_stream << "layout bounds on " << edge_name << " border must start at edge";
+        *out_err = err_stream.str();
+        return false;
+      }
+      *layout_end = length - range.start;
+    }
+  }
+  return true;
+}
+
+static int32_t CalculateSegmentCount(const std::vector<Range>& stretch_regions, int32_t length) {
+  if (stretch_regions.size() == 0) {
+    return 0;
+  }
+
+  const bool start_is_fixed = stretch_regions.front().start != 0;
+  const bool end_is_fixed = stretch_regions.back().end != length;
+  int32_t modifier = 0;
+  if (start_is_fixed && end_is_fixed) {
+    modifier = 1;
+  } else if (!start_is_fixed && !end_is_fixed) {
+    modifier = -1;
+  }
+  return static_cast<int32_t>(stretch_regions.size()) * 2 + modifier;
+}
+
+static uint32_t GetRegionColor(uint8_t** rows, const Bounds& region) {
+  // Sample the first pixel to compare against.
+  const uint32_t expected_color = NinePatch::PackRGBA(rows[region.top] + region.left * 4);
+  for (int32_t y = region.top; y < region.bottom; y++) {
+    const uint8_t* row = rows[y];
+    for (int32_t x = region.left; x < region.right; x++) {
+      const uint32_t color = NinePatch::PackRGBA(row + x * 4);
+      if (get_alpha(color) == 0) {
+        // The color is transparent.
+        // If the expectedColor is not transparent, NO_COLOR.
+        if (get_alpha(expected_color) != 0) {
+          return android::Res_png_9patch::NO_COLOR;
+        }
+      } else if (color != expected_color) {
+        return android::Res_png_9patch::NO_COLOR;
+      }
+    }
+  }
+
+  if (get_alpha(expected_color) == 0) {
+    return android::Res_png_9patch::TRANSPARENT_COLOR;
+  }
+  return expected_color;
+}
+
+// Fills out_colors with each 9-patch section's color. If the whole section is
+// transparent,
+// it gets the special TRANSPARENT color. If the whole section is the same
+// color, it is assigned
+// that color. Otherwise it gets the special NO_COLOR color.
+//
+// Note that the rows contain the 9-patch 1px border, and the indices in the
+// stretch regions are
+// already offset to exclude the border. This means that each time the rows are
+// accessed,
+// the indices must be offset by 1.
+//
+// width and height also include the 9-patch 1px border.
+static void CalculateRegionColors(uint8_t** rows,
+                                  const std::vector<Range>& horizontal_stretch_regions,
+                                  const std::vector<Range>& vertical_stretch_regions,
+                                  const int32_t width, const int32_t height,
+                                  std::vector<uint32_t>* out_colors) {
+  int32_t next_top = 0;
+  Bounds bounds;
+  auto row_iter = vertical_stretch_regions.begin();
+  while (next_top != height) {
+    if (row_iter != vertical_stretch_regions.end()) {
+      if (next_top != row_iter->start) {
+        // This is a fixed segment.
+        // Offset the bounds by 1 to accommodate the border.
+        bounds.top = next_top + 1;
+        bounds.bottom = row_iter->start + 1;
+        next_top = row_iter->start;
+      } else {
+        // This is a stretchy segment.
+        // Offset the bounds by 1 to accommodate the border.
+        bounds.top = row_iter->start + 1;
+        bounds.bottom = row_iter->end + 1;
+        next_top = row_iter->end;
+        ++row_iter;
+      }
+    } else {
+      // This is the end, fixed section.
+      // Offset the bounds by 1 to accommodate the border.
+      bounds.top = next_top + 1;
+      bounds.bottom = height + 1;
+      next_top = height;
+    }
+
+    int32_t next_left = 0;
+    auto col_iter = horizontal_stretch_regions.begin();
+    while (next_left != width) {
+      if (col_iter != horizontal_stretch_regions.end()) {
+        if (next_left != col_iter->start) {
+          // This is a fixed segment.
+          // Offset the bounds by 1 to accommodate the border.
+          bounds.left = next_left + 1;
+          bounds.right = col_iter->start + 1;
+          next_left = col_iter->start;
+        } else {
+          // This is a stretchy segment.
+          // Offset the bounds by 1 to accommodate the border.
+          bounds.left = col_iter->start + 1;
+          bounds.right = col_iter->end + 1;
+          next_left = col_iter->end;
+          ++col_iter;
+        }
+      } else {
+        // This is the end, fixed section.
+        // Offset the bounds by 1 to accommodate the border.
+        bounds.left = next_left + 1;
+        bounds.right = width + 1;
+        next_left = width;
+      }
+      out_colors->push_back(GetRegionColor(rows, bounds));
+    }
+  }
+}
+
+// Calculates the insets of a row/column of pixels based on where the largest
+// alpha value begins
+// (on both sides).
+template <typename ImageLine>
+static void FindOutlineInsets(const ImageLine* image_line, int32_t* out_start, int32_t* out_end) {
+  *out_start = 0;
+  *out_end = 0;
+
+  const int32_t length = image_line->GetLength();
+  if (length < 3) {
+    return;
+  }
+
+  // If the length is odd, we want both sides to process the center pixel,
+  // so we use two different midpoints (to account for < and <= in the different
+  // loops).
+  const int32_t mid2 = length / 2;
+  const int32_t mid1 = mid2 + (length % 2);
+
+  uint32_t max_alpha = 0;
+  for (int32_t i = 0; i < mid1 && max_alpha != 0xff; i++) {
+    uint32_t alpha = get_alpha(image_line->GetColor(i));
+    if (alpha > max_alpha) {
+      max_alpha = alpha;
+      *out_start = i;
+    }
+  }
+
+  max_alpha = 0;
+  for (int32_t i = length - 1; i >= mid2 && max_alpha != 0xff; i--) {
+    uint32_t alpha = get_alpha(image_line->GetColor(i));
+    if (alpha > max_alpha) {
+      max_alpha = alpha;
+      *out_end = length - (i + 1);
+    }
+  }
+  return;
+}
+
+template <typename ImageLine>
+static uint32_t FindMaxAlpha(const ImageLine* image_line) {
+  const int32_t length = image_line->GetLength();
+  uint32_t max_alpha = 0;
+  for (int32_t idx = 0; idx < length && max_alpha != 0xff; idx++) {
+    uint32_t alpha = get_alpha(image_line->GetColor(idx));
+    if (alpha > max_alpha) {
+      max_alpha = alpha;
+    }
+  }
+  return max_alpha;
+}
+
+// Pack the pixels in as 0xAARRGGBB (as 9-patch expects it).
+uint32_t NinePatch::PackRGBA(const uint8_t* pixel) {
+  return (pixel[3] << 24) | (pixel[0] << 16) | (pixel[1] << 8) | pixel[2];
+}
+
+std::unique_ptr<NinePatch> NinePatch::Create(uint8_t** rows, const int32_t width,
+                                             const int32_t height, std::string* out_err) {
+  if (width < 3 || height < 3) {
+    *out_err = "image must be at least 3x3 (1x1 image with 1 pixel border)";
+    return {};
+  }
+
+  std::vector<Range> horizontal_padding;
+  std::vector<Range> horizontal_layout_bounds;
+  std::vector<Range> vertical_padding;
+  std::vector<Range> vertical_layout_bounds;
+  std::vector<Range> unexpected_ranges;
+  std::unique_ptr<ColorValidator> color_validator;
+
+  if (rows[0][3] == 0) {
+    color_validator = std::make_unique<TransparentNeutralColorValidator>();
+  } else if (PackRGBA(rows[0]) == kColorOpaqueWhite) {
+    color_validator = std::make_unique<WhiteNeutralColorValidator>();
+  } else {
+    *out_err = "top-left corner pixel must be either opaque white or transparent";
+    return {};
+  }
+
+  // Private constructor, can't use make_unique.
+  auto nine_patch = std::unique_ptr<NinePatch>(new NinePatch());
+
+  HorizontalImageLine top_row(rows, 0, 0, width);
+  if (!FillRanges(&top_row, color_validator.get(), &nine_patch->horizontal_stretch_regions,
+                  &unexpected_ranges, out_err)) {
+    return {};
+  }
+
+  if (!unexpected_ranges.empty()) {
+    const Range& range = unexpected_ranges[0];
+    std::stringstream err_stream;
+    err_stream << "found unexpected optical bounds (red pixel) on top border "
+               << "at x=" << range.start + 1;
+    *out_err = err_stream.str();
+    return {};
+  }
+
+  VerticalImageLine left_col(rows, 0, 0, height);
+  if (!FillRanges(&left_col, color_validator.get(), &nine_patch->vertical_stretch_regions,
+                  &unexpected_ranges, out_err)) {
+    return {};
+  }
+
+  if (!unexpected_ranges.empty()) {
+    const Range& range = unexpected_ranges[0];
+    std::stringstream err_stream;
+    err_stream << "found unexpected optical bounds (red pixel) on left border "
+               << "at y=" << range.start + 1;
+    return {};
+  }
+
+  HorizontalImageLine bottom_row(rows, 0, height - 1, width);
+  if (!FillRanges(&bottom_row, color_validator.get(), &horizontal_padding,
+                  &horizontal_layout_bounds, out_err)) {
+    return {};
+  }
+
+  if (!PopulateBounds(horizontal_padding, horizontal_layout_bounds,
+                      nine_patch->horizontal_stretch_regions, width - 2, &nine_patch->padding.left,
+                      &nine_patch->padding.right, &nine_patch->layout_bounds.left,
+                      &nine_patch->layout_bounds.right, "bottom", out_err)) {
+    return {};
+  }
+
+  VerticalImageLine right_col(rows, width - 1, 0, height);
+  if (!FillRanges(&right_col, color_validator.get(), &vertical_padding, &vertical_layout_bounds,
+                  out_err)) {
+    return {};
+  }
+
+  if (!PopulateBounds(vertical_padding, vertical_layout_bounds,
+                      nine_patch->vertical_stretch_regions, height - 2, &nine_patch->padding.top,
+                      &nine_patch->padding.bottom, &nine_patch->layout_bounds.top,
+                      &nine_patch->layout_bounds.bottom, "right", out_err)) {
+    return {};
+  }
+
+  // Fill the region colors of the 9-patch.
+  const int32_t num_rows = CalculateSegmentCount(nine_patch->horizontal_stretch_regions, width - 2);
+  const int32_t num_cols = CalculateSegmentCount(nine_patch->vertical_stretch_regions, height - 2);
+  if ((int64_t)num_rows * (int64_t)num_cols > 0x7f) {
+    *out_err = "too many regions in 9-patch";
+    return {};
+  }
+
+  nine_patch->region_colors.reserve(num_rows * num_cols);
+  CalculateRegionColors(rows, nine_patch->horizontal_stretch_regions,
+                        nine_patch->vertical_stretch_regions, width - 2, height - 2,
+                        &nine_patch->region_colors);
+
+  // Compute the outline based on opacity.
+
+  // Find left and right extent of 9-patch content on center row.
+  HorizontalImageLine mid_row(rows, 1, height / 2, width - 2);
+  FindOutlineInsets(&mid_row, &nine_patch->outline.left, &nine_patch->outline.right);
+
+  // Find top and bottom extent of 9-patch content on center column.
+  VerticalImageLine mid_col(rows, width / 2, 1, height - 2);
+  FindOutlineInsets(&mid_col, &nine_patch->outline.top, &nine_patch->outline.bottom);
+
+  const int32_t outline_width = (width - 2) - nine_patch->outline.left - nine_patch->outline.right;
+  const int32_t outline_height =
+      (height - 2) - nine_patch->outline.top - nine_patch->outline.bottom;
+
+  // Find the largest alpha value within the outline area.
+  HorizontalImageLine outline_mid_row(rows, 1 + nine_patch->outline.left,
+                                      1 + nine_patch->outline.top + (outline_height / 2),
+                                      outline_width);
+  VerticalImageLine outline_mid_col(rows, 1 + nine_patch->outline.left + (outline_width / 2),
+                                    1 + nine_patch->outline.top, outline_height);
+  nine_patch->outline_alpha =
+      std::max(FindMaxAlpha(&outline_mid_row), FindMaxAlpha(&outline_mid_col));
+
+  // Assuming the image is a round rect, compute the radius by marching
+  // diagonally from the top left corner towards the center.
+  DiagonalImageLine diagonal(rows, 1 + nine_patch->outline.left, 1 + nine_patch->outline.top, 1, 1,
+                             std::min(outline_width, outline_height));
+  int32_t top_left, bottom_right;
+  FindOutlineInsets(&diagonal, &top_left, &bottom_right);
+
+  /* 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
+   */
+  nine_patch->outline_radius = 3.4142f * top_left;
+  return nine_patch;
+}
+
+std::unique_ptr<uint8_t[]> NinePatch::SerializeBase(size_t* outLen) const {
+  android::Res_png_9patch data;
+  data.numXDivs = static_cast<uint8_t>(horizontal_stretch_regions.size()) * 2;
+  data.numYDivs = static_cast<uint8_t>(vertical_stretch_regions.size()) * 2;
+  data.numColors = static_cast<uint8_t>(region_colors.size());
+  data.paddingLeft = padding.left;
+  data.paddingRight = padding.right;
+  data.paddingTop = padding.top;
+  data.paddingBottom = padding.bottom;
+
+  auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[data.serializedSize()]);
+  android::Res_png_9patch::serialize(data, (const int32_t*)horizontal_stretch_regions.data(),
+                                     (const int32_t*)vertical_stretch_regions.data(),
+                                     region_colors.data(), buffer.get());
+  // Convert to file endianness.
+  reinterpret_cast<android::Res_png_9patch*>(buffer.get())->deviceToFile();
+
+  *outLen = data.serializedSize();
+  return buffer;
+}
+
+std::unique_ptr<uint8_t[]> NinePatch::SerializeLayoutBounds(size_t* out_len) const {
+  size_t chunk_len = sizeof(uint32_t) * 4;
+  auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[chunk_len]);
+  uint8_t* cursor = buffer.get();
+
+  memcpy(cursor, &layout_bounds.left, sizeof(layout_bounds.left));
+  cursor += sizeof(layout_bounds.left);
+
+  memcpy(cursor, &layout_bounds.top, sizeof(layout_bounds.top));
+  cursor += sizeof(layout_bounds.top);
+
+  memcpy(cursor, &layout_bounds.right, sizeof(layout_bounds.right));
+  cursor += sizeof(layout_bounds.right);
+
+  memcpy(cursor, &layout_bounds.bottom, sizeof(layout_bounds.bottom));
+  cursor += sizeof(layout_bounds.bottom);
+
+  *out_len = chunk_len;
+  return buffer;
+}
+
+std::unique_ptr<uint8_t[]> NinePatch::SerializeRoundedRectOutline(size_t* out_len) const {
+  size_t chunk_len = sizeof(uint32_t) * 6;
+  auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[chunk_len]);
+  uint8_t* cursor = buffer.get();
+
+  memcpy(cursor, &outline.left, sizeof(outline.left));
+  cursor += sizeof(outline.left);
+
+  memcpy(cursor, &outline.top, sizeof(outline.top));
+  cursor += sizeof(outline.top);
+
+  memcpy(cursor, &outline.right, sizeof(outline.right));
+  cursor += sizeof(outline.right);
+
+  memcpy(cursor, &outline.bottom, sizeof(outline.bottom));
+  cursor += sizeof(outline.bottom);
+
+  *((float*)cursor) = outline_radius;
+  cursor += sizeof(outline_radius);
+
+  *((uint32_t*)cursor) = outline_alpha;
+
+  *out_len = chunk_len;
+  return buffer;
+}
+
+::std::ostream& operator<<(::std::ostream& out, const Range& range) {
+  return out << "[" << range.start << ", " << range.end << ")";
+}
+
+::std::ostream& operator<<(::std::ostream& out, const Bounds& bounds) {
+  return out << "l=" << bounds.left << " t=" << bounds.top << " r=" << bounds.right
+             << " b=" << bounds.bottom;
+}
+
+template <typename T>
+std::ostream& operator<<(std::ostream& os, const std::vector<T>& v) {
+  for (int i = 0; i < v.size(); ++i) {
+    os << v[i];
+    if (i != v.size() - 1) os << " ";
+  }
+  return os;
+}
+
+::std::ostream& operator<<(::std::ostream& out, const NinePatch& nine_patch) {
+  return out << "horizontalStretch:" << nine_patch.horizontal_stretch_regions
+             << " verticalStretch:" << nine_patch.vertical_stretch_regions
+             << " padding: " << nine_patch.padding << ", bounds: " << nine_patch.layout_bounds
+             << ", outline: " << nine_patch.outline << " rad=" << nine_patch.outline_radius
+             << " alpha=" << nine_patch.outline_alpha;
+}
+
+}  // namespace android
diff --git a/libs/androidfw/Png.cpp b/libs/androidfw/Png.cpp
new file mode 100644
index 0000000..fb45cd9
--- /dev/null
+++ b/libs/androidfw/Png.cpp
@@ -0,0 +1,1259 @@
+/*
+ * 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 "androidfw/Png.h"
+
+#include <png.h>
+#include <zlib.h>
+
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "android-base/strings.h"
+#include "androidfw/BigBuffer.h"
+#include "androidfw/ResourceTypes.h"
+#include "androidfw/Source.h"
+
+namespace android {
+
+constexpr bool kDebug = false;
+
+struct PngInfo {
+  ~PngInfo() {
+    for (png_bytep row : rows) {
+      if (row != nullptr) {
+        delete[] row;
+      }
+    }
+
+    delete[] xDivs;
+    delete[] yDivs;
+  }
+
+  void* serialize9Patch() {
+    void* serialized = Res_png_9patch::serialize(info9Patch, xDivs, yDivs, colors.data());
+    reinterpret_cast<Res_png_9patch*>(serialized)->deviceToFile();
+    return serialized;
+  }
+
+  uint32_t width = 0;
+  uint32_t height = 0;
+  std::vector<png_bytep> rows;
+
+  bool is9Patch = false;
+  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) {
+  BigBuffer* outBuffer = reinterpret_cast<BigBuffer*>(png_get_io_ptr(writePtr));
+  png_bytep buf = outBuffer->NextBlock<png_byte>(length);
+  memcpy(buf, data, length);
+}
+
+static void flushDataToStream(png_structp /*writePtr*/) {
+}
+
+static void logWarning(png_structp readPtr, png_const_charp warningMessage) {
+  IDiagnostics* diag = reinterpret_cast<IDiagnostics*>(png_get_error_ptr(readPtr));
+  diag->Warn(DiagMessage() << warningMessage);
+}
+
+static bool readPng(IDiagnostics* diag, png_structp readPtr, png_infop infoPtr, PngInfo* outInfo) {
+  if (setjmp(png_jmpbuf(readPtr))) {
+    diag->Error(DiagMessage() << "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(Res_png_9patch* inPatch, void* data) {
+  size_t patchSize = inPatch->serializedSize();
+  void* newData = malloc(patchSize);
+  memcpy(newData, data, patchSize);
+  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");
+            }
+        }
+    }
+}*/
+
+#ifdef MAX
+#undef MAX
+#endif
+#ifdef ABS
+#undef ABS
+#endif
+
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+#define ABS(a) ((a) < 0 ? -(a) : (a))
+
+static void analyze_image(IDiagnostics* diag, 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) {
+      diag->Note(DiagMessage() << "forcing image to gray (max deviation = " << maxGrayDeviation
+                               << ")");
+      *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(IDiagnostics* diag, png_structp writePtr, png_infop infoPtr, PngInfo* info,
+                     int grayScaleTolerance) {
+  if (setjmp(png_jmpbuf(writePtr))) {
+    diag->Error(DiagMessage() << "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) {
+    diag->Note(DiagMessage() << "writing image: w = " << info->width << ", h = " << info->height);
+  }
+
+  png_color rgbPalette[256];
+  png_byte alphaPalette[256];
+  bool hasTransparency;
+  int paletteEntries;
+
+  analyze_image(diag, *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:
+        diag->Note(DiagMessage() << "has " << paletteEntries << " colors"
+                                 << (hasTransparency ? " (with alpha)" : "")
+                                 << ", using PNG_COLOR_TYPE_PALLETTE");
+        break;
+      case PNG_COLOR_TYPE_GRAY:
+        diag->Note(DiagMessage() << "is opaque gray, using PNG_COLOR_TYPE_GRAY");
+        break;
+      case PNG_COLOR_TYPE_GRAY_ALPHA:
+        diag->Note(DiagMessage() << "is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA");
+        break;
+      case PNG_COLOR_TYPE_RGB:
+        diag->Note(DiagMessage() << "is opaque RGB, using PNG_COLOR_TYPE_RGB");
+        break;
+      case PNG_COLOR_TYPE_RGB_ALPHA:
+        diag->Note(DiagMessage() << "is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA");
+        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) {
+      diag->Note(DiagMessage() << "adding 9-patch info..");
+    }
+    memcpy((char*)unknowns[pIndex].name, "npTc", 5);
+    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;
+    memcpy((char*)unknowns[oIndex].name, "npOl", 5);
+    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;
+      memcpy((char*)unknowns[bIndex].name, "npLb", 5);
+      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) {
+    diag->Note(DiagMessage() << "image written: w = " << width << ", h = " << height
+                             << ", d = " << bitDepth << ", colors = " << colorType
+                             << ", inter = " << interlaceType << ", comp = " << compressionType);
+  }
+  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 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 Res_png_9patch::NO_COLOR;
+        }
+      } else if (p[0] != color[0] || p[1] != color[1] || p[2] != color[2] || p[3] != color[3]) {
+        return Res_png_9patch::NO_COLOR;
+      }
+    }
+    top++;
+  }
+
+  if (color[3] == 0) {
+    return 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 != 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, BigBuffer* outBuffer,
+                  const PngOptions& options) {
+  png_byte signature[kPngSignatureSize];
+
+  // Read the PNG signature first.
+  if (!input->read(reinterpret_cast<char*>(signature), kPngSignatureSize)) {
+    mDiag->Error(DiagMessage() << strerror(errno));
+    return false;
+  }
+
+  // If the PNG signature doesn't match, bail early.
+  if (png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
+    mDiag->Error(DiagMessage() << "not a valid png file");
+    return false;
+  }
+
+  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) {
+    mDiag->Error(DiagMessage() << "failed to allocate read ptr");
+    goto bail;
+  }
+
+  infoPtr = png_create_info_struct(readPtr);
+  if (!infoPtr) {
+    mDiag->Error(DiagMessage() << "failed to allocate info ptr");
+    goto bail;
+  }
+
+  png_set_error_fn(readPtr, reinterpret_cast<png_voidp>(mDiag), nullptr, logWarning);
+
+  // Set the read function to read from std::istream.
+  png_set_read_fn(readPtr, (png_voidp)input, readDataFromStream);
+
+  if (!readPng(mDiag, readPtr, infoPtr, &pngInfo)) {
+    goto bail;
+  }
+
+  if (android::base::EndsWith(source.path, ".9.png")) {
+    std::string errorMsg;
+    if (!do9Patch(&pngInfo, &errorMsg)) {
+      mDiag->Error(DiagMessage() << errorMsg);
+      goto bail;
+    }
+  }
+
+  writePtr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr);
+  if (!writePtr) {
+    mDiag->Error(DiagMessage() << "failed to allocate write ptr");
+    goto bail;
+  }
+
+  writeInfoPtr = png_create_info_struct(writePtr);
+  if (!writeInfoPtr) {
+    mDiag->Error(DiagMessage() << "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)outBuffer, writeDataToStream, flushDataToStream);
+
+  if (!writePng(mDiag, writePtr, writeInfoPtr, &pngInfo, options.grayscale_tolerance)) {
+    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 android
diff --git a/libs/androidfw/PngChunkFilter.cpp b/libs/androidfw/PngChunkFilter.cpp
new file mode 100644
index 0000000..331b948
--- /dev/null
+++ b/libs/androidfw/PngChunkFilter.cpp
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2016 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 "android-base/stringprintf.h"
+#include "android-base/strings.h"
+#include "androidfw/Png.h"
+#include "androidfw/Streams.h"
+#include "androidfw/StringPiece.h"
+
+using ::android::base::StringPrintf;
+
+namespace android {
+
+static constexpr const char* kPngSignature = "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a";
+
+// Useful helper function that encodes individual bytes into a uint32
+// at compile time.
+constexpr uint32_t u32(uint8_t a, uint8_t b, uint8_t c, uint8_t d) {
+  return (((uint32_t)a) << 24) | (((uint32_t)b) << 16) | (((uint32_t)c) << 8) | ((uint32_t)d);
+}
+
+// Allow list of PNG chunk types that we want to keep in the resulting PNG.
+enum PngChunkTypes {
+  kPngChunkIHDR = u32(73, 72, 68, 82),
+  kPngChunkIDAT = u32(73, 68, 65, 84),
+  kPngChunkIEND = u32(73, 69, 78, 68),
+  kPngChunkPLTE = u32(80, 76, 84, 69),
+  kPngChunktRNS = u32(116, 82, 78, 83),
+  kPngChunksRGB = u32(115, 82, 71, 66),
+};
+
+static uint32_t Peek32LE(const char* data) {
+  uint32_t word = ((uint32_t)data[0]) & 0x000000ff;
+  word <<= 8;
+  word |= ((uint32_t)data[1]) & 0x000000ff;
+  word <<= 8;
+  word |= ((uint32_t)data[2]) & 0x000000ff;
+  word <<= 8;
+  word |= ((uint32_t)data[3]) & 0x000000ff;
+  return word;
+}
+
+static bool IsPngChunkAllowed(uint32_t type) {
+  switch (type) {
+    case kPngChunkIHDR:
+    case kPngChunkIDAT:
+    case kPngChunkIEND:
+    case kPngChunkPLTE:
+    case kPngChunktRNS:
+    case kPngChunksRGB:
+      return true;
+    default:
+      return false;
+  }
+}
+
+PngChunkFilter::PngChunkFilter(StringPiece data) : data_(data) {
+  if (android::base::StartsWith(data_, kPngSignature)) {
+    window_start_ = 0;
+    window_end_ = kPngSignatureSize;
+  } else {
+    error_msg_ = "file does not start with PNG signature";
+  }
+}
+
+bool PngChunkFilter::ConsumeWindow(const void** buffer, size_t* len) {
+  if (window_start_ != window_end_) {
+    // We have bytes to give from our window.
+    const size_t bytes_read = window_end_ - window_start_;
+    *buffer = data_.data() + window_start_;
+    *len = bytes_read;
+    window_start_ = window_end_;
+    return true;
+  }
+  return false;
+}
+
+bool PngChunkFilter::Next(const void** buffer, size_t* len) {
+  if (HadError()) {
+    return false;
+  }
+
+  // In case BackUp was called, we must consume the window.
+  if (ConsumeWindow(buffer, len)) {
+    return true;
+  }
+
+  // Advance the window as far as possible (until we meet a chunk that
+  // we want to strip).
+  while (window_end_ < data_.size()) {
+    // Chunk length (4 bytes) + type (4 bytes) + crc32 (4 bytes) = 12 bytes.
+    const size_t kMinChunkHeaderSize = 3 * sizeof(uint32_t);
+
+    // Is there enough room for a chunk header?
+    if (data_.size() - window_end_ < kMinChunkHeaderSize) {
+      error_msg_ = StringPrintf("Not enough space for a PNG chunk @ byte %zu/%zu", window_end_,
+                                data_.size());
+      return false;
+    }
+
+    // Verify the chunk length.
+    const uint32_t chunk_len = Peek32LE(data_.data() + window_end_);
+    if ((size_t)chunk_len > data_.size() - window_end_ - kMinChunkHeaderSize) {
+      // Overflow.
+      const uint32_t chunk_type = Peek32LE(data_.data() + window_end_ + sizeof(uint32_t));
+      error_msg_ = StringPrintf(
+          "PNG chunk type %08x is too large: chunk length is %zu but chunk "
+          "starts at byte %zu/%zu",
+          chunk_type, (size_t)chunk_len, window_end_ + kMinChunkHeaderSize, data_.size());
+      return false;
+    }
+
+    // Do we strip this chunk?
+    const uint32_t chunk_type = Peek32LE(data_.data() + window_end_ + sizeof(uint32_t));
+    if (IsPngChunkAllowed(chunk_type)) {
+      // Advance the window to include this chunk.
+      window_end_ += kMinChunkHeaderSize + chunk_len;
+
+      // Special case the IEND chunk, which MUST appear last and libpng stops parsing once it hits
+      // such a chunk (let's do the same).
+      if (chunk_type == kPngChunkIEND) {
+        // Truncate the data to the end of this chunk. There may be garbage trailing after
+        // (b/38169876)
+        data_ = data_.substr(0, window_end_);
+        break;
+      }
+
+    } else {
+      // We want to strip this chunk. If we accumulated a window,
+      // we must return the window now.
+      if (window_start_ != window_end_) {
+        break;
+      }
+
+      // The window is empty, so we can advance past this chunk
+      // and keep looking for the next good chunk,
+      window_end_ += kMinChunkHeaderSize + chunk_len;
+      window_start_ = window_end_;
+    }
+  }
+
+  if (ConsumeWindow(buffer, len)) {
+    return true;
+  }
+  return false;
+}
+
+void PngChunkFilter::BackUp(size_t count) {
+  if (HadError()) {
+    return;
+  }
+  window_start_ -= count;
+}
+
+bool PngChunkFilter::Rewind() {
+  if (HadError()) {
+    return false;
+  }
+  window_start_ = 0;
+  window_end_ = kPngSignatureSize;
+  return true;
+}
+}  // namespace android
diff --git a/libs/androidfw/PngCrunch.cpp b/libs/androidfw/PngCrunch.cpp
new file mode 100644
index 0000000..cf3c0ee
--- /dev/null
+++ b/libs/androidfw/PngCrunch.cpp
@@ -0,0 +1,730 @@
+/*
+ * Copyright (C) 2016 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 <png.h>
+#include <zlib.h>
+
+#include <algorithm>
+#include <unordered_map>
+#include <unordered_set>
+
+#include "android-base/errors.h"
+#include "android-base/logging.h"
+#include "android-base/macros.h"
+#include "androidfw/Png.h"
+
+namespace android {
+
+// Custom deleter that destroys libpng read and info structs.
+class PngReadStructDeleter {
+ public:
+  PngReadStructDeleter(png_structp read_ptr, png_infop info_ptr)
+      : read_ptr_(read_ptr), info_ptr_(info_ptr) {
+  }
+
+  ~PngReadStructDeleter() {
+    png_destroy_read_struct(&read_ptr_, &info_ptr_, nullptr);
+  }
+
+ private:
+  png_structp read_ptr_;
+  png_infop info_ptr_;
+
+  DISALLOW_COPY_AND_ASSIGN(PngReadStructDeleter);
+};
+
+// Custom deleter that destroys libpng write and info structs.
+class PngWriteStructDeleter {
+ public:
+  PngWriteStructDeleter(png_structp write_ptr, png_infop info_ptr)
+      : write_ptr_(write_ptr), info_ptr_(info_ptr) {
+  }
+
+  ~PngWriteStructDeleter() {
+    png_destroy_write_struct(&write_ptr_, &info_ptr_);
+  }
+
+ private:
+  png_structp write_ptr_;
+  png_infop info_ptr_;
+
+  DISALLOW_COPY_AND_ASSIGN(PngWriteStructDeleter);
+};
+
+// Custom warning logging method that uses IDiagnostics.
+static void LogWarning(png_structp png_ptr, png_const_charp warning_msg) {
+  android::IDiagnostics* diag = (android::IDiagnostics*)png_get_error_ptr(png_ptr);
+  diag->Warn(android::DiagMessage() << warning_msg);
+}
+
+// Custom error logging method that uses IDiagnostics.
+static void LogError(png_structp png_ptr, png_const_charp error_msg) {
+  android::IDiagnostics* diag = (android::IDiagnostics*)png_get_error_ptr(png_ptr);
+  diag->Error(android::DiagMessage() << error_msg);
+
+  // Causes libpng to longjmp to the spot where setjmp was set. This is how libpng does
+  // error handling. If this custom error handler method were to return, libpng would, by
+  // default, print the error message to stdout and call the same png_longjmp method.
+  png_longjmp(png_ptr, 1);
+}
+
+static void ReadDataFromStream(png_structp png_ptr, png_bytep buffer, png_size_t len) {
+  InputStream* in = (InputStream*)png_get_io_ptr(png_ptr);
+
+  const void* in_buffer;
+  size_t in_len;
+  if (!in->Next(&in_buffer, &in_len)) {
+    if (in->HadError()) {
+      std::stringstream error_msg_builder;
+      error_msg_builder << "failed reading from input";
+      if (!in->GetError().empty()) {
+        error_msg_builder << ": " << in->GetError();
+      }
+      std::string err = error_msg_builder.str();
+      png_error(png_ptr, err.c_str());
+    }
+    return;
+  }
+
+  const size_t bytes_read = std::min(in_len, len);
+  memcpy(buffer, in_buffer, bytes_read);
+  if (bytes_read != in_len) {
+    in->BackUp(in_len - bytes_read);
+  }
+}
+
+static void WriteDataToStream(png_structp png_ptr, png_bytep buffer, png_size_t len) {
+  OutputStream* out = (OutputStream*)png_get_io_ptr(png_ptr);
+
+  void* out_buffer;
+  size_t out_len;
+  while (len > 0) {
+    if (!out->Next(&out_buffer, &out_len)) {
+      if (out->HadError()) {
+        std::stringstream err_msg_builder;
+        err_msg_builder << "failed writing to output";
+        if (!out->GetError().empty()) {
+          err_msg_builder << ": " << out->GetError();
+        }
+        std::string err = out->GetError();
+        png_error(png_ptr, err.c_str());
+      }
+      return;
+    }
+
+    const size_t bytes_written = std::min(out_len, len);
+    memcpy(out_buffer, buffer, bytes_written);
+
+    // Advance the input buffer.
+    buffer += bytes_written;
+    len -= bytes_written;
+
+    // Advance the output buffer.
+    out_len -= bytes_written;
+  }
+
+  // If the entire output buffer wasn't used, backup.
+  if (out_len > 0) {
+    out->BackUp(out_len);
+  }
+}
+
+std::unique_ptr<Image> ReadPng(InputStream* in, IDiagnostics* diag) {
+  // Read the first 8 bytes of the file looking for the PNG signature.
+  // Bail early if it does not match.
+  const png_byte* signature;
+  size_t buffer_size;
+  if (!in->Next((const void**)&signature, &buffer_size)) {
+    if (in->HadError()) {
+      diag->Error(android::DiagMessage() << "failed to read PNG signature: " << in->GetError());
+    } else {
+      diag->Error(android::DiagMessage() << "not enough data for PNG signature");
+    }
+    return {};
+  }
+
+  if (buffer_size < kPngSignatureSize || png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
+    diag->Error(android::DiagMessage() << "file signature does not match PNG signature");
+    return {};
+  }
+
+  // Start at the beginning of the first chunk.
+  in->BackUp(buffer_size - kPngSignatureSize);
+
+  // Create and initialize the png_struct with the default error and warning handlers.
+  // The header version is also passed in to ensure that this was built against the same
+  // version of libpng.
+  png_structp read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
+  if (read_ptr == nullptr) {
+    diag->Error(android::DiagMessage() << "failed to create libpng read png_struct");
+    return {};
+  }
+
+  // Create and initialize the memory for image header and data.
+  png_infop info_ptr = png_create_info_struct(read_ptr);
+  if (info_ptr == nullptr) {
+    diag->Error(android::DiagMessage() << "failed to create libpng read png_info");
+    png_destroy_read_struct(&read_ptr, nullptr, nullptr);
+    return {};
+  }
+
+  // Automatically release PNG resources at end of scope.
+  PngReadStructDeleter png_read_deleter(read_ptr, info_ptr);
+
+  // libpng uses longjmp to jump to an error handling routine.
+  // setjmp will only return true if it was jumped to, aka there was
+  // an error.
+  if (setjmp(png_jmpbuf(read_ptr))) {
+    return {};
+  }
+
+  // Handle warnings ourselves via IDiagnostics.
+  png_set_error_fn(read_ptr, (png_voidp)&diag, LogError, LogWarning);
+
+  // Set up the read functions which read from our custom data sources.
+  png_set_read_fn(read_ptr, (png_voidp)in, ReadDataFromStream);
+
+  // Skip the signature that we already read.
+  png_set_sig_bytes(read_ptr, kPngSignatureSize);
+
+  // Read the chunk headers.
+  png_read_info(read_ptr, info_ptr);
+
+  // Extract image meta-data from the various chunk headers.
+  uint32_t width, height;
+  int bit_depth, color_type, interlace_method, compression_method, filter_method;
+  png_get_IHDR(read_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_method,
+               &compression_method, &filter_method);
+
+  // When the image is read, expand it so that it is in RGBA 8888 format
+  // so that image handling is uniform.
+
+  if (color_type == PNG_COLOR_TYPE_PALETTE) {
+    png_set_palette_to_rgb(read_ptr);
+  }
+
+  if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) {
+    png_set_expand_gray_1_2_4_to_8(read_ptr);
+  }
+
+  if (png_get_valid(read_ptr, info_ptr, PNG_INFO_tRNS)) {
+    png_set_tRNS_to_alpha(read_ptr);
+  }
+
+  if (bit_depth == 16) {
+    png_set_strip_16(read_ptr);
+  }
+
+  if (!(color_type & PNG_COLOR_MASK_ALPHA)) {
+    png_set_add_alpha(read_ptr, 0xFF, PNG_FILLER_AFTER);
+  }
+
+  if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
+    png_set_gray_to_rgb(read_ptr);
+  }
+
+  if (interlace_method != PNG_INTERLACE_NONE) {
+    png_set_interlace_handling(read_ptr);
+  }
+
+  // Once all the options for reading have been set, we need to flush
+  // them to libpng.
+  png_read_update_info(read_ptr, info_ptr);
+
+  // 9-patch uses int32_t to index images, so we cap the image dimensions to
+  // something
+  // that can always be represented by 9-patch.
+  if (width > std::numeric_limits<int32_t>::max() || height > std::numeric_limits<int32_t>::max()) {
+    diag->Error(android::DiagMessage()
+                << "PNG image dimensions are too large: " << width << "x" << height);
+    return {};
+  }
+
+  std::unique_ptr<Image> output_image = std::make_unique<Image>();
+  output_image->width = static_cast<int32_t>(width);
+  output_image->height = static_cast<int32_t>(height);
+
+  const size_t row_bytes = png_get_rowbytes(read_ptr, info_ptr);
+  CHECK(row_bytes == 4 * width);  // RGBA
+
+  // Allocate one large block to hold the image.
+  output_image->data = std::unique_ptr<uint8_t[]>(new uint8_t[height * row_bytes]);
+
+  // Create an array of rows that index into the data block.
+  output_image->rows = std::unique_ptr<uint8_t*[]>(new uint8_t*[height]);
+  for (uint32_t h = 0; h < height; h++) {
+    output_image->rows[h] = output_image->data.get() + (h * row_bytes);
+  }
+
+  // Actually read the image pixels.
+  png_read_image(read_ptr, output_image->rows.get());
+
+  // Finish reading. This will read any other chunks after the image data.
+  png_read_end(read_ptr, info_ptr);
+
+  return output_image;
+}
+
+// Experimentally chosen constant to be added to the overhead of using color type
+// PNG_COLOR_TYPE_PALETTE to account for the uncompressability of the palette chunk.
+// Without this, many small PNGs encoded with palettes are larger after compression than
+// the same PNGs encoded as RGBA.
+constexpr static const size_t kPaletteOverheadConstant = 1024u * 10u;
+
+// Pick a color type by which to encode the image, based on which color type will take
+// the least amount of disk space.
+//
+// 9-patch images traditionally have not been encoded with palettes.
+// The original rationale was to avoid dithering until after scaling,
+// but I don't think this would be an issue with palettes. Either way,
+// our naive size estimation tends to be wrong for small images like 9-patches
+// and using palettes balloons the size of the resulting 9-patch.
+// In order to not regress in size, restrict 9-patch to not use palettes.
+
+// The options are:
+//
+// - RGB
+// - RGBA
+// - RGB + cheap alpha
+// - Color palette
+// - Color palette + cheap alpha
+// - Color palette + alpha palette
+// - Grayscale
+// - Grayscale + cheap alpha
+// - Grayscale + alpha
+//
+static int PickColorType(int32_t width, int32_t height, bool grayscale,
+                         bool convertible_to_grayscale, bool has_nine_patch,
+                         size_t color_palette_size, size_t alpha_palette_size) {
+  const size_t palette_chunk_size = 16 + color_palette_size * 3;
+  const size_t alpha_chunk_size = 16 + alpha_palette_size;
+  const size_t color_alpha_data_chunk_size = 16 + 4 * width * height;
+  const size_t color_data_chunk_size = 16 + 3 * width * height;
+  const size_t grayscale_alpha_data_chunk_size = 16 + 2 * width * height;
+  const size_t palette_data_chunk_size = 16 + width * height;
+
+  if (grayscale) {
+    if (alpha_palette_size == 0) {
+      // This is the smallest the data can be.
+      return PNG_COLOR_TYPE_GRAY;
+    } else if (color_palette_size <= 256 && !has_nine_patch) {
+      // This grayscale has alpha and can fit within a palette.
+      // See if it is worth fitting into a palette.
+      const size_t palette_threshold = palette_chunk_size + alpha_chunk_size +
+                                       palette_data_chunk_size + kPaletteOverheadConstant;
+      if (grayscale_alpha_data_chunk_size > palette_threshold) {
+        return PNG_COLOR_TYPE_PALETTE;
+      }
+    }
+    return PNG_COLOR_TYPE_GRAY_ALPHA;
+  }
+
+  if (color_palette_size <= 256 && !has_nine_patch) {
+    // This image can fit inside a palette. Let's see if it is worth it.
+    size_t total_size_with_palette = palette_data_chunk_size + palette_chunk_size;
+    size_t total_size_without_palette = color_data_chunk_size;
+    if (alpha_palette_size > 0) {
+      total_size_with_palette += alpha_palette_size;
+      total_size_without_palette = color_alpha_data_chunk_size;
+    }
+
+    if (total_size_without_palette > total_size_with_palette + kPaletteOverheadConstant) {
+      return PNG_COLOR_TYPE_PALETTE;
+    }
+  }
+
+  if (convertible_to_grayscale) {
+    if (alpha_palette_size == 0) {
+      return PNG_COLOR_TYPE_GRAY;
+    } else {
+      return PNG_COLOR_TYPE_GRAY_ALPHA;
+    }
+  }
+
+  if (alpha_palette_size == 0) {
+    return PNG_COLOR_TYPE_RGB;
+  }
+  return PNG_COLOR_TYPE_RGBA;
+}
+
+// Assigns indices to the color and alpha palettes, encodes them, and then invokes
+// png_set_PLTE/png_set_tRNS.
+// This must be done before writing image data.
+// Image data must be transformed to use the indices assigned within the palette.
+static void WritePalette(png_structp write_ptr, png_infop write_info_ptr,
+                         std::unordered_map<uint32_t, int>* color_palette,
+                         std::unordered_set<uint32_t>* alpha_palette) {
+  CHECK(color_palette->size() <= 256);
+  CHECK(alpha_palette->size() <= 256);
+
+  // Populate the PNG palette struct and assign indices to the color palette.
+
+  // Colors in the alpha palette should have smaller indices.
+  // This will ensure that we can truncate the alpha palette if it is
+  // smaller than the color palette.
+  int index = 0;
+  for (uint32_t color : *alpha_palette) {
+    (*color_palette)[color] = index++;
+  }
+
+  // Assign the rest of the entries.
+  for (auto& entry : *color_palette) {
+    if (entry.second == -1) {
+      entry.second = index++;
+    }
+  }
+
+  // Create the PNG color palette struct.
+  auto color_palette_bytes = std::unique_ptr<png_color[]>(new png_color[color_palette->size()]);
+
+  std::unique_ptr<png_byte[]> alpha_palette_bytes;
+  if (!alpha_palette->empty()) {
+    alpha_palette_bytes = std::unique_ptr<png_byte[]>(new png_byte[alpha_palette->size()]);
+  }
+
+  for (const auto& entry : *color_palette) {
+    const uint32_t color = entry.first;
+    const int index = entry.second;
+    CHECK(index >= 0);
+    CHECK(static_cast<size_t>(index) < color_palette->size());
+
+    png_colorp slot = color_palette_bytes.get() + index;
+    slot->red = color >> 24;
+    slot->green = color >> 16;
+    slot->blue = color >> 8;
+
+    const png_byte alpha = color & 0x000000ff;
+    if (alpha != 0xff && alpha_palette_bytes) {
+      CHECK(static_cast<size_t>(index) < alpha_palette->size());
+      alpha_palette_bytes[index] = alpha;
+    }
+  }
+
+  // The bytes get copied here, so it is safe to release color_palette_bytes at
+  // the end of function
+  // scope.
+  png_set_PLTE(write_ptr, write_info_ptr, color_palette_bytes.get(), color_palette->size());
+
+  if (alpha_palette_bytes) {
+    png_set_tRNS(write_ptr, write_info_ptr, alpha_palette_bytes.get(), alpha_palette->size(),
+                 nullptr);
+  }
+}
+
+// Write the 9-patch custom PNG chunks to write_info_ptr. This must be done
+// before writing image data.
+static void WriteNinePatch(png_structp write_ptr, png_infop write_info_ptr,
+                           const NinePatch* nine_patch) {
+  // The order of the chunks is important.
+  // 9-patch code in older platforms expects the 9-patch chunk to be last.
+
+  png_unknown_chunk unknown_chunks[3];
+  memset(unknown_chunks, 0, sizeof(unknown_chunks));
+
+  size_t index = 0;
+  size_t chunk_len = 0;
+
+  std::unique_ptr<uint8_t[]> serialized_outline =
+      nine_patch->SerializeRoundedRectOutline(&chunk_len);
+  strcpy((char*)unknown_chunks[index].name, "npOl");
+  unknown_chunks[index].size = chunk_len;
+  unknown_chunks[index].data = (png_bytep)serialized_outline.get();
+  unknown_chunks[index].location = PNG_HAVE_PLTE;
+  index++;
+
+  std::unique_ptr<uint8_t[]> serialized_layout_bounds;
+  if (nine_patch->layout_bounds.nonZero()) {
+    serialized_layout_bounds = nine_patch->SerializeLayoutBounds(&chunk_len);
+    strcpy((char*)unknown_chunks[index].name, "npLb");
+    unknown_chunks[index].size = chunk_len;
+    unknown_chunks[index].data = (png_bytep)serialized_layout_bounds.get();
+    unknown_chunks[index].location = PNG_HAVE_PLTE;
+    index++;
+  }
+
+  std::unique_ptr<uint8_t[]> serialized_nine_patch = nine_patch->SerializeBase(&chunk_len);
+  strcpy((char*)unknown_chunks[index].name, "npTc");
+  unknown_chunks[index].size = chunk_len;
+  unknown_chunks[index].data = (png_bytep)serialized_nine_patch.get();
+  unknown_chunks[index].location = PNG_HAVE_PLTE;
+  index++;
+
+  // Handle all unknown chunks. We are manually setting the chunks here,
+  // so we will only ever handle our custom chunks.
+  png_set_keep_unknown_chunks(write_ptr, PNG_HANDLE_CHUNK_ALWAYS, nullptr, 0);
+
+  // Set the actual chunks here. The data gets copied, so our buffers can
+  // safely go out of scope.
+  png_set_unknown_chunks(write_ptr, write_info_ptr, unknown_chunks, index);
+}
+
+bool WritePng(const Image* image, const NinePatch* nine_patch, OutputStream* out,
+              const PngOptions& options, IDiagnostics* diag, bool verbose) {
+  // Create and initialize the write png_struct with the default error and
+  // warning handlers.
+  // The header version is also passed in to ensure that this was built against the same
+  // version of libpng.
+  png_structp write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
+  if (write_ptr == nullptr) {
+    diag->Error(android::DiagMessage() << "failed to create libpng write png_struct");
+    return false;
+  }
+
+  // Allocate memory to store image header data.
+  png_infop write_info_ptr = png_create_info_struct(write_ptr);
+  if (write_info_ptr == nullptr) {
+    diag->Error(android::DiagMessage() << "failed to create libpng write png_info");
+    png_destroy_write_struct(&write_ptr, nullptr);
+    return false;
+  }
+
+  // Automatically release PNG resources at end of scope.
+  PngWriteStructDeleter png_write_deleter(write_ptr, write_info_ptr);
+
+  // libpng uses longjmp to jump to error handling routines.
+  // setjmp will return true only if it was jumped to, aka, there was an error.
+  if (setjmp(png_jmpbuf(write_ptr))) {
+    return false;
+  }
+
+  // Handle warnings with our IDiagnostics.
+  png_set_error_fn(write_ptr, (png_voidp)&diag, LogError, LogWarning);
+
+  // Set up the write functions which write to our custom data sources.
+  png_set_write_fn(write_ptr, (png_voidp)out, WriteDataToStream, nullptr);
+
+  // We want small files and can take the performance hit to achieve this goal.
+  png_set_compression_level(write_ptr, Z_BEST_COMPRESSION);
+
+  // Begin analysis of the image data.
+  // 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 (palette).
+  std::unordered_map<uint32_t, int> color_palette;
+  std::unordered_set<uint32_t> alpha_palette;
+  bool needs_to_zero_rgb_channels_of_transparent_pixels = false;
+  bool grayscale = true;
+  int max_gray_deviation = 0;
+
+  for (int32_t y = 0; y < image->height; y++) {
+    const uint8_t* row = image->rows[y];
+    for (int32_t x = 0; x < image->width; x++) {
+      int red = *row++;
+      int green = *row++;
+      int blue = *row++;
+      int alpha = *row++;
+
+      if (alpha == 0) {
+        // The color is completely transparent.
+        // For purposes of palettes and grayscale optimization,
+        // treat all channels as 0x00.
+        needs_to_zero_rgb_channels_of_transparent_pixels =
+            needs_to_zero_rgb_channels_of_transparent_pixels ||
+            (red != 0 || green != 0 || blue != 0);
+        red = green = blue = 0;
+      }
+
+      // Insert the color into the color palette.
+      const uint32_t color = red << 24 | green << 16 | blue << 8 | alpha;
+      color_palette[color] = -1;
+
+      // If the pixel has non-opaque alpha, insert it into the
+      // alpha palette.
+      if (alpha != 0xff) {
+        alpha_palette.insert(color);
+      }
+
+      // Check if the image is indeed grayscale.
+      if (grayscale) {
+        if (red != green || red != blue) {
+          grayscale = false;
+        }
+      }
+
+      // Calculate the gray scale deviation so that it can be compared
+      // with the threshold.
+      max_gray_deviation = std::max(std::abs(red - green), max_gray_deviation);
+      max_gray_deviation = std::max(std::abs(green - blue), max_gray_deviation);
+      max_gray_deviation = std::max(std::abs(blue - red), max_gray_deviation);
+    }
+  }
+
+  if (verbose) {
+    android::DiagMessage msg;
+    msg << " paletteSize=" << color_palette.size() << " alphaPaletteSize=" << alpha_palette.size()
+        << " maxGrayDeviation=" << max_gray_deviation
+        << " grayScale=" << (grayscale ? "true" : "false");
+    diag->Note(msg);
+  }
+
+  const bool convertible_to_grayscale = max_gray_deviation <= options.grayscale_tolerance;
+
+  const int new_color_type =
+      PickColorType(image->width, image->height, grayscale, convertible_to_grayscale,
+                    nine_patch != nullptr, color_palette.size(), alpha_palette.size());
+
+  if (verbose) {
+    android::DiagMessage msg;
+    msg << "encoding PNG ";
+    if (nine_patch) {
+      msg << "(with 9-patch) as ";
+    }
+    switch (new_color_type) {
+      case PNG_COLOR_TYPE_GRAY:
+        msg << "GRAY";
+        break;
+      case PNG_COLOR_TYPE_GRAY_ALPHA:
+        msg << "GRAY + ALPHA";
+        break;
+      case PNG_COLOR_TYPE_RGB:
+        msg << "RGB";
+        break;
+      case PNG_COLOR_TYPE_RGB_ALPHA:
+        msg << "RGBA";
+        break;
+      case PNG_COLOR_TYPE_PALETTE:
+        msg << "PALETTE";
+        break;
+      default:
+        msg << "unknown type " << new_color_type;
+        break;
+    }
+    diag->Note(msg);
+  }
+
+  png_set_IHDR(write_ptr, write_info_ptr, image->width, image->height, 8, new_color_type,
+               PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
+
+  if (new_color_type & PNG_COLOR_MASK_PALETTE) {
+    // Assigns indices to the palette, and writes the encoded palette to the
+    // libpng writePtr.
+    WritePalette(write_ptr, write_info_ptr, &color_palette, &alpha_palette);
+    png_set_filter(write_ptr, 0, PNG_NO_FILTERS);
+  } else {
+    png_set_filter(write_ptr, 0, PNG_ALL_FILTERS);
+  }
+
+  if (nine_patch) {
+    WriteNinePatch(write_ptr, write_info_ptr, nine_patch);
+  }
+
+  // Flush our updates to the header.
+  png_write_info(write_ptr, write_info_ptr);
+
+  // Write out each row of image data according to its encoding.
+  if (new_color_type == PNG_COLOR_TYPE_PALETTE) {
+    // 1 byte/pixel.
+    auto out_row = std::unique_ptr<png_byte[]>(new png_byte[image->width]);
+
+    for (int32_t y = 0; y < image->height; y++) {
+      png_const_bytep in_row = image->rows[y];
+      for (int32_t x = 0; x < image->width; x++) {
+        int rr = *in_row++;
+        int gg = *in_row++;
+        int bb = *in_row++;
+        int aa = *in_row++;
+        if (aa == 0) {
+          // Zero out color channels when transparent.
+          rr = gg = bb = 0;
+        }
+
+        const uint32_t color = rr << 24 | gg << 16 | bb << 8 | aa;
+        const int idx = color_palette[color];
+        CHECK(idx != -1);
+        out_row[x] = static_cast<png_byte>(idx);
+      }
+      png_write_row(write_ptr, out_row.get());
+    }
+  } else if (new_color_type == PNG_COLOR_TYPE_GRAY || new_color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
+    const size_t bpp = new_color_type == PNG_COLOR_TYPE_GRAY ? 1 : 2;
+    auto out_row = std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]);
+
+    for (int32_t y = 0; y < image->height; y++) {
+      png_const_bytep in_row = image->rows[y];
+      for (int32_t x = 0; x < image->width; x++) {
+        int rr = in_row[x * 4];
+        int gg = in_row[x * 4 + 1];
+        int bb = in_row[x * 4 + 2];
+        int aa = in_row[x * 4 + 3];
+        if (aa == 0) {
+          // Zero out the gray channel when transparent.
+          rr = gg = bb = 0;
+        }
+
+        if (grayscale) {
+          // The image was already grayscale, red == green == blue.
+          out_row[x * bpp] = in_row[x * 4];
+        } else {
+          // The image is convertible to grayscale, use linear-luminance of
+          // sRGB colorspace:
+          // https://en.wikipedia.org/wiki/Grayscale#Colorimetric_.28luminance-preserving.29_conversion_to_grayscale
+          out_row[x * bpp] = (png_byte)(rr * 0.2126f + gg * 0.7152f + bb * 0.0722f);
+        }
+
+        if (bpp == 2) {
+          // Write out alpha if we have it.
+          out_row[x * bpp + 1] = aa;
+        }
+      }
+      png_write_row(write_ptr, out_row.get());
+    }
+  } else if (new_color_type == PNG_COLOR_TYPE_RGB || new_color_type == PNG_COLOR_TYPE_RGBA) {
+    const size_t bpp = new_color_type == PNG_COLOR_TYPE_RGB ? 3 : 4;
+    if (needs_to_zero_rgb_channels_of_transparent_pixels) {
+      // The source RGBA data can't be used as-is, because we need to zero out
+      // the RGB values of transparent pixels.
+      auto out_row = std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]);
+
+      for (int32_t y = 0; y < image->height; y++) {
+        png_const_bytep in_row = image->rows[y];
+        for (int32_t x = 0; x < image->width; x++) {
+          int rr = *in_row++;
+          int gg = *in_row++;
+          int bb = *in_row++;
+          int aa = *in_row++;
+          if (aa == 0) {
+            // Zero out the RGB channels when transparent.
+            rr = gg = bb = 0;
+          }
+          out_row[x * bpp] = rr;
+          out_row[x * bpp + 1] = gg;
+          out_row[x * bpp + 2] = bb;
+          if (bpp == 4) {
+            out_row[x * bpp + 3] = aa;
+          }
+        }
+        png_write_row(write_ptr, out_row.get());
+      }
+    } else {
+      // The source image can be used as-is, just tell libpng whether or not to
+      // ignore the alpha channel.
+      if (new_color_type == PNG_COLOR_TYPE_RGB) {
+        // Delete the extraneous alpha values that we appended to our buffer
+        // when reading the original values.
+        png_set_filler(write_ptr, 0, PNG_FILLER_AFTER);
+      }
+      png_write_image(write_ptr, image->rows.get());
+    }
+  } else {
+    LOG(FATAL) << "unreachable";
+  }
+
+  png_write_end(write_ptr, write_info_ptr);
+  return true;
+}
+
+}  // namespace android
diff --git a/libs/androidfw/include/androidfw/BigBufferStream.h b/libs/androidfw/include/androidfw/BigBufferStream.h
new file mode 100644
index 0000000..e55fe0d
--- /dev/null
+++ b/libs/androidfw/include/androidfw/BigBufferStream.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#pragma once
+
+#include "BigBuffer.h"
+#include "Streams.h"
+
+namespace android {
+
+class BigBufferInputStream : public KnownSizeInputStream {
+ public:
+  inline explicit BigBufferInputStream(const BigBuffer* buffer)
+      : buffer_(buffer), iter_(buffer->begin()) {
+  }
+  virtual ~BigBufferInputStream() = default;
+
+  bool Next(const void** data, size_t* size) override;
+
+  void BackUp(size_t count) override;
+
+  bool CanRewind() const override;
+
+  bool Rewind() override;
+
+  size_t ByteCount() const override;
+
+  bool HadError() const override;
+
+  size_t TotalSize() const override;
+
+  bool ReadFullyAtOffset(void* data, size_t byte_count, off64_t offset) override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(BigBufferInputStream);
+
+  const BigBuffer* buffer_;
+  BigBuffer::const_iterator iter_;
+  size_t offset_ = 0;
+  size_t bytes_read_ = 0;
+};
+
+class BigBufferOutputStream : public OutputStream {
+ public:
+  inline explicit BigBufferOutputStream(BigBuffer* buffer) : buffer_(buffer) {
+  }
+  virtual ~BigBufferOutputStream() = default;
+
+  bool Next(void** data, size_t* size) override;
+
+  void BackUp(size_t count) override;
+
+  size_t ByteCount() const override;
+
+  bool HadError() const override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(BigBufferOutputStream);
+
+  BigBuffer* buffer_;
+};
+
+}  // namespace android
\ No newline at end of file
diff --git a/libs/androidfw/include/androidfw/FileStream.h b/libs/androidfw/include/androidfw/FileStream.h
new file mode 100644
index 0000000..fb84a91
--- /dev/null
+++ b/libs/androidfw/include/androidfw/FileStream.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#pragma once
+
+#include <memory>
+#include <string>
+
+#include "Streams.h"
+#include "android-base/macros.h"
+#include "android-base/unique_fd.h"
+
+namespace android {
+
+constexpr size_t kDefaultBufferCapacity = 4096u;
+
+class FileInputStream : public InputStream {
+ public:
+  explicit FileInputStream(const std::string& path,
+                           size_t buffer_capacity = kDefaultBufferCapacity);
+
+  // Take ownership of `fd`.
+  explicit FileInputStream(int fd, size_t buffer_capacity = kDefaultBufferCapacity);
+
+  bool Next(const void** data, size_t* size) override;
+
+  void BackUp(size_t count) override;
+
+  size_t ByteCount() const override;
+
+  bool HadError() const override;
+
+  std::string GetError() const override;
+
+  bool ReadFullyAtOffset(void* data, size_t byte_count, off64_t offset) override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(FileInputStream);
+
+  android::base::unique_fd fd_;
+  std::string error_;
+  std::unique_ptr<uint8_t[]> buffer_;
+  size_t buffer_capacity_ = 0u;
+  size_t buffer_offset_ = 0u;
+  size_t buffer_size_ = 0u;
+  size_t total_byte_count_ = 0u;
+};
+
+class FileOutputStream : public OutputStream {
+ public:
+  explicit FileOutputStream(const std::string& path,
+                            size_t buffer_capacity = kDefaultBufferCapacity);
+
+  // Does not take ownership of `fd`.
+  explicit FileOutputStream(int fd, size_t buffer_capacity = kDefaultBufferCapacity);
+
+  // Takes ownership of `fd`.
+  explicit FileOutputStream(android::base::unique_fd fd,
+                            size_t buffer_capacity = kDefaultBufferCapacity);
+
+  ~FileOutputStream();
+
+  bool Next(void** data, size_t* size) override;
+
+  // Immediately flushes out the contents of the buffer to disk.
+  bool Flush();
+
+  void BackUp(size_t count) override;
+
+  size_t ByteCount() const override;
+
+  bool HadError() const override;
+
+  std::string GetError() const override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(FileOutputStream);
+
+  bool FlushImpl();
+
+  android::base::unique_fd owned_fd_;
+  int fd_;
+  std::string error_;
+  std::unique_ptr<uint8_t[]> buffer_;
+  size_t buffer_capacity_ = 0u;
+  size_t buffer_offset_ = 0u;
+  size_t total_byte_count_ = 0u;
+};
+
+}  // namespace android
\ No newline at end of file
diff --git a/libs/androidfw/include/androidfw/Image.h b/libs/androidfw/include/androidfw/Image.h
new file mode 100644
index 0000000..c18c34c
--- /dev/null
+++ b/libs/androidfw/include/androidfw/Image.h
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "android-base/macros.h"
+
+namespace android {
+
+/**
+ * An in-memory image, loaded from disk, with pixels in RGBA_8888 format.
+ */
+class Image {
+ public:
+  explicit Image() = default;
+
+  /**
+   * A `height` sized array of pointers, where each element points to a
+   * `width` sized row of RGBA_8888 pixels.
+   */
+  std::unique_ptr<uint8_t*[]> rows;
+
+  /**
+   * The width of the image in RGBA_8888 pixels. This is int32_t because of
+   * 9-patch data
+   * format limitations.
+   */
+  int32_t width = 0;
+
+  /**
+   * The height of the image in RGBA_8888 pixels. This is int32_t because of
+   * 9-patch data
+   * format limitations.
+   */
+  int32_t height = 0;
+
+  /**
+   * Buffer to the raw image data stored sequentially.
+   * Use `rows` to access the data on a row-by-row basis.
+   */
+  std::unique_ptr<uint8_t[]> data;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(Image);
+};
+
+/**
+ * A range of pixel values, starting at 'start' and ending before 'end'
+ * exclusive. Or rather [a, b).
+ */
+struct Range {
+  int32_t start = 0;
+  int32_t end = 0;
+
+  explicit Range() = default;
+  inline explicit Range(int32_t s, int32_t e) : start(s), end(e) {
+  }
+};
+
+inline bool operator==(const Range& left, const Range& right) {
+  return left.start == right.start && left.end == right.end;
+}
+
+/**
+ * Inset lengths from all edges of a rectangle. `left` and `top` are measured
+ * from the left and top
+ * edges, while `right` and `bottom` are measured from the right and bottom
+ * edges, respectively.
+ */
+struct Bounds {
+  int32_t left = 0;
+  int32_t top = 0;
+  int32_t right = 0;
+  int32_t bottom = 0;
+
+  explicit Bounds() = default;
+  inline explicit Bounds(int32_t l, int32_t t, int32_t r, int32_t b)
+      : left(l), top(t), right(r), bottom(b) {
+  }
+
+  bool nonZero() const;
+};
+
+inline bool Bounds::nonZero() const {
+  return left != 0 || top != 0 || right != 0 || bottom != 0;
+}
+
+inline bool operator==(const Bounds& left, const Bounds& right) {
+  return left.left == right.left && left.top == right.top && left.right == right.right &&
+         left.bottom == right.bottom;
+}
+
+/**
+ * Contains 9-patch data from a source image. All measurements exclude the 1px
+ * border of the
+ * source 9-patch image.
+ */
+class NinePatch {
+ public:
+  static std::unique_ptr<NinePatch> Create(uint8_t** rows, const int32_t width,
+                                           const int32_t height, std::string* err_out);
+
+  /**
+   * Packs the RGBA_8888 data pointed to by pixel into a uint32_t
+   * with format 0xAARRGGBB (the way 9-patch expects it).
+   */
+  static uint32_t PackRGBA(const uint8_t* pixel);
+
+  /**
+   * 9-patch content padding/insets. All positions are relative to the 9-patch
+   * NOT including the 1px thick source border.
+   */
+  Bounds padding;
+
+  /**
+   * Optical layout bounds/insets. This overrides the padding for
+   * layout purposes. All positions are relative to the 9-patch
+   * NOT including the 1px thick source border.
+   * See
+   * https://developer.android.com/about/versions/android-4.3.html#OpticalBounds
+   */
+  Bounds layout_bounds;
+
+  /**
+   * Outline of the image, calculated based on opacity.
+   */
+  Bounds outline;
+
+  /**
+   * The computed radius of the outline. If non-zero, the outline is a
+   * rounded-rect.
+   */
+  float outline_radius = 0.0f;
+
+  /**
+   * The largest alpha value within the outline.
+   */
+  uint32_t outline_alpha = 0x000000ffu;
+
+  /**
+   * Horizontal regions of the image that are stretchable.
+   * All positions are relative to the 9-patch
+   * NOT including the 1px thick source border.
+   */
+  std::vector<Range> horizontal_stretch_regions;
+
+  /**
+   * Vertical regions of the image that are stretchable.
+   * All positions are relative to the 9-patch
+   * NOT including the 1px thick source border.
+   */
+  std::vector<Range> vertical_stretch_regions;
+
+  /**
+   * The colors within each region, fixed or stretchable.
+   * For w*h regions, the color of region (x,y) is addressable
+   * via index y*w + x.
+   */
+  std::vector<uint32_t> region_colors;
+
+  /**
+   * Returns serialized data containing the original basic 9-patch meta data.
+   * Optical layout bounds and round rect outline data must be serialized
+   * separately using SerializeOpticalLayoutBounds() and
+   * SerializeRoundedRectOutline().
+   */
+  std::unique_ptr<uint8_t[]> SerializeBase(size_t* out_len) const;
+
+  /**
+   * Serializes the layout bounds.
+   */
+  std::unique_ptr<uint8_t[]> SerializeLayoutBounds(size_t* out_len) const;
+
+  /**
+   * Serializes the rounded-rect outline.
+   */
+  std::unique_ptr<uint8_t[]> SerializeRoundedRectOutline(size_t* out_len) const;
+
+ private:
+  explicit NinePatch() = default;
+
+  DISALLOW_COPY_AND_ASSIGN(NinePatch);
+};
+
+::std::ostream& operator<<(::std::ostream& out, const Range& range);
+::std::ostream& operator<<(::std::ostream& out, const Bounds& bounds);
+::std::ostream& operator<<(::std::ostream& out, const NinePatch& nine_patch);
+
+}  // namespace android
\ No newline at end of file
diff --git a/libs/androidfw/include/androidfw/Png.h b/libs/androidfw/include/androidfw/Png.h
new file mode 100644
index 0000000..2ece43e
--- /dev/null
+++ b/libs/androidfw/include/androidfw/Png.h
@@ -0,0 +1,100 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <string>
+
+#include "BigBuffer.h"
+#include "IDiagnostics.h"
+#include "Image.h"
+#include "Source.h"
+#include "Streams.h"
+#include "android-base/macros.h"
+
+namespace android {
+// Size in bytes of the PNG signature.
+constexpr size_t kPngSignatureSize = 8u;
+
+struct PngOptions {
+  int grayscale_tolerance = 0;
+};
+
+/**
+ * Deprecated. Removing once new PNG crunching code is proved to be correct.
+ */
+class Png {
+ public:
+  explicit Png(IDiagnostics* diag) : mDiag(diag) {
+  }
+
+  bool process(const Source& source, std::istream* input, BigBuffer* outBuffer,
+               const PngOptions& options);
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(Png);
+
+  IDiagnostics* mDiag;
+};
+
+/**
+ * An InputStream that filters out unimportant PNG chunks.
+ */
+class PngChunkFilter : public InputStream {
+ public:
+  explicit PngChunkFilter(StringPiece data);
+  virtual ~PngChunkFilter() = default;
+
+  bool Next(const void** buffer, size_t* len) override;
+  void BackUp(size_t count) override;
+
+  bool CanRewind() const override {
+    return true;
+  }
+  bool Rewind() override;
+  size_t ByteCount() const override {
+    return window_start_;
+  }
+
+  bool HadError() const override {
+    return !error_msg_.empty();
+  }
+  std::string GetError() const override {
+    return error_msg_;
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PngChunkFilter);
+
+  bool ConsumeWindow(const void** buffer, size_t* len);
+
+  StringPiece data_;
+  size_t window_start_ = 0;
+  size_t window_end_ = 0;
+  std::string error_msg_;
+};
+/**
+ * Reads a PNG from the InputStream into memory as an RGBA Image.
+ */
+std::unique_ptr<Image> ReadPng(InputStream* in, IDiagnostics* diag);
+
+/**
+ * Writes the RGBA Image, with optional 9-patch meta-data, into the OutputStream
+ * as a PNG.
+ */
+bool WritePng(const Image* image, const NinePatch* nine_patch, OutputStream* out,
+              const PngOptions& options, IDiagnostics* diag, bool verbose);
+}  // namespace android
\ No newline at end of file
diff --git a/libs/androidfw/include/androidfw/Streams.h b/libs/androidfw/include/androidfw/Streams.h
new file mode 100644
index 0000000..2daf0e2
--- /dev/null
+++ b/libs/androidfw/include/androidfw/Streams.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#pragma once
+
+#include <string>
+#include "android-base/off64_t.h"
+
+namespace android {
+
+// InputStream interface that mimics protobuf's ZeroCopyInputStream,
+// with added error handling methods to better report issues.
+class InputStream {
+ public:
+  virtual ~InputStream() = default;
+
+  // Returns a chunk of data for reading. data and size must not be nullptr.
+  // Returns true so long as there is more data to read, returns false if an error occurred
+  // or no data remains. If an error occurred, check HadError().
+  // The stream owns the buffer returned from this method and the buffer is invalidated
+  // anytime another mutable method is called.
+  virtual bool Next(const void** data, size_t* size) = 0;
+
+  // Backup count bytes, where count is smaller or equal to the size of the last buffer returned
+  // from Next().
+  // Useful when the last block returned from Next() wasn't fully read.
+  virtual void BackUp(size_t count) = 0;
+
+  // Returns true if this InputStream can rewind. If so, Rewind() can be called.
+  virtual bool CanRewind() const {
+    return false;
+  };
+
+  // Rewinds the stream to the beginning so it can be read again.
+  // Returns true if the rewind succeeded.
+  // This does nothing if CanRewind() returns false.
+  virtual bool Rewind() {
+    return false;
+  }
+
+  // Returns the number of bytes that have been read from the stream.
+  virtual size_t ByteCount() const = 0;
+
+  // Returns an error message if HadError() returned true.
+  virtual std::string GetError() const {
+    return {};
+  }
+
+  // Returns true if an error occurred. Errors are permanent.
+  virtual bool HadError() const = 0;
+
+  virtual bool ReadFullyAtOffset(void* data, size_t byte_count, off64_t offset) {
+    (void)data;
+    (void)byte_count;
+    (void)offset;
+    return false;
+  }
+};
+
+// A sub-InputStream interface that knows the total size of its stream.
+class KnownSizeInputStream : public InputStream {
+ public:
+  virtual size_t TotalSize() const = 0;
+};
+
+// OutputStream interface that mimics protobuf's ZeroCopyOutputStream,
+// with added error handling methods to better report issues.
+class OutputStream {
+ public:
+  virtual ~OutputStream() = default;
+
+  // Returns a buffer to which data can be written to. The data written to this buffer will
+  // eventually be written to the stream. Call BackUp() if the data written doesn't occupy the
+  // entire buffer.
+  // Return false if there was an error.
+  // The stream owns the buffer returned from this method and the buffer is invalidated
+  // anytime another mutable method is called.
+  virtual bool Next(void** data, size_t* size) = 0;
+
+  // Backup count bytes, where count is smaller or equal to the size of the last buffer returned
+  // from Next().
+  // Useful for when the last block returned from Next() wasn't fully written to.
+  virtual void BackUp(size_t count) = 0;
+
+  // Returns the number of bytes that have been written to the stream.
+  virtual size_t ByteCount() const = 0;
+
+  // Returns an error message if HadError() returned true.
+  virtual std::string GetError() const {
+    return {};
+  }
+
+  // Returns true if an error occurred. Errors are permanent.
+  virtual bool HadError() const = 0;
+};
+
+}  // namespace android
\ No newline at end of file
diff --git a/libs/androidfw/tests/FileStream_test.cpp b/libs/androidfw/tests/FileStream_test.cpp
new file mode 100644
index 0000000..9785975
--- /dev/null
+++ b/libs/androidfw/tests/FileStream_test.cpp
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2017 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 "androidfw/FileStream.h"
+#include "androidfw/StringPiece.h"
+
+#include "android-base/file.h"
+#include "android-base/macros.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+using ::android::StringPiece;
+using ::testing::Eq;
+using ::testing::NotNull;
+using ::testing::StrEq;
+
+namespace android {
+
+TEST(FileInputStreamTest, NextAndBackup) {
+  std::string input = "this is a cool string";
+  TemporaryFile file;
+  ASSERT_THAT(TEMP_FAILURE_RETRY(write(file.fd, input.c_str(), input.size())), Eq(21));
+  lseek64(file.fd, 0, SEEK_SET);
+
+  // Use a small buffer size so that we can call Next() a few times.
+  FileInputStream in(file.release(), 10u);
+  ASSERT_FALSE(in.HadError());
+  EXPECT_THAT(in.ByteCount(), Eq(0u));
+
+  const void* buffer;
+  size_t size;
+  ASSERT_TRUE(in.Next(&buffer, &size)) << in.GetError();
+  ASSERT_THAT(size, Eq(10u));
+  ASSERT_THAT(buffer, NotNull());
+  EXPECT_THAT(in.ByteCount(), Eq(10u));
+  EXPECT_THAT(StringPiece(reinterpret_cast<const char*>(buffer), size), Eq("this is a "));
+
+  ASSERT_TRUE(in.Next(&buffer, &size));
+  ASSERT_THAT(size, Eq(10u));
+  ASSERT_THAT(buffer, NotNull());
+  EXPECT_THAT(in.ByteCount(), Eq(20u));
+  EXPECT_THAT(StringPiece(reinterpret_cast<const char*>(buffer), size), Eq("cool strin"));
+
+  in.BackUp(5u);
+  EXPECT_THAT(in.ByteCount(), Eq(15u));
+
+  ASSERT_TRUE(in.Next(&buffer, &size));
+  ASSERT_THAT(size, Eq(5u));
+  ASSERT_THAT(buffer, NotNull());
+  ASSERT_THAT(in.ByteCount(), Eq(20u));
+  EXPECT_THAT(StringPiece(reinterpret_cast<const char*>(buffer), size), Eq("strin"));
+
+  // Backup 1 more than possible. Should clamp.
+  in.BackUp(11u);
+  EXPECT_THAT(in.ByteCount(), Eq(10u));
+
+  ASSERT_TRUE(in.Next(&buffer, &size));
+  ASSERT_THAT(size, Eq(10u));
+  ASSERT_THAT(buffer, NotNull());
+  ASSERT_THAT(in.ByteCount(), Eq(20u));
+  EXPECT_THAT(StringPiece(reinterpret_cast<const char*>(buffer), size), Eq("cool strin"));
+
+  ASSERT_TRUE(in.Next(&buffer, &size));
+  ASSERT_THAT(size, Eq(1u));
+  ASSERT_THAT(buffer, NotNull());
+  ASSERT_THAT(in.ByteCount(), Eq(21u));
+  EXPECT_THAT(StringPiece(reinterpret_cast<const char*>(buffer), size), Eq("g"));
+
+  EXPECT_FALSE(in.Next(&buffer, &size));
+  EXPECT_FALSE(in.HadError());
+}
+
+TEST(FileOutputStreamTest, NextAndBackup) {
+  const std::string input = "this is a cool string";
+
+  TemporaryFile file;
+
+  FileOutputStream out(file.fd, 10u);
+  ASSERT_FALSE(out.HadError());
+  EXPECT_THAT(out.ByteCount(), Eq(0u));
+
+  void* buffer;
+  size_t size;
+  ASSERT_TRUE(out.Next(&buffer, &size));
+  ASSERT_THAT(size, Eq(10u));
+  ASSERT_THAT(buffer, NotNull());
+  EXPECT_THAT(out.ByteCount(), Eq(10u));
+  memcpy(reinterpret_cast<char*>(buffer), input.c_str(), size);
+
+  ASSERT_TRUE(out.Next(&buffer, &size));
+  ASSERT_THAT(size, Eq(10u));
+  ASSERT_THAT(buffer, NotNull());
+  EXPECT_THAT(out.ByteCount(), Eq(20u));
+  memcpy(reinterpret_cast<char*>(buffer), input.c_str() + 10u, size);
+
+  ASSERT_TRUE(out.Next(&buffer, &size));
+  ASSERT_THAT(size, Eq(10u));
+  ASSERT_THAT(buffer, NotNull());
+  EXPECT_THAT(out.ByteCount(), Eq(30u));
+  reinterpret_cast<char*>(buffer)[0] = input[20u];
+  out.BackUp(size - 1);
+  EXPECT_THAT(out.ByteCount(), Eq(21u));
+
+  ASSERT_TRUE(out.Flush());
+
+  lseek64(file.fd, 0, SEEK_SET);
+
+  std::string actual;
+  ASSERT_TRUE(android::base::ReadFdToString(file.fd, &actual));
+  EXPECT_THAT(actual, StrEq(input));
+}
+
+}  // namespace android
diff --git a/libs/androidfw/tests/NinePatch_test.cpp b/libs/androidfw/tests/NinePatch_test.cpp
new file mode 100644
index 0000000..7ee8e9e
--- /dev/null
+++ b/libs/androidfw/tests/NinePatch_test.cpp
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2016 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 "androidfw/Image.h"
+#include "androidfw/ResourceTypes.h"
+#include "gtest/gtest.h"
+
+namespace android {
+
+// Pixels are in RGBA_8888 packing.
+
+#define RED "\xff\x00\x00\xff"
+#define BLUE "\x00\x00\xff\xff"
+#define GREEN "\xff\x00\x00\xff"
+#define GR_70 "\xff\x00\x00\xb3"
+#define GR_50 "\xff\x00\x00\x80"
+#define GR_20 "\xff\x00\x00\x33"
+#define BLACK "\x00\x00\x00\xff"
+#define WHITE "\xff\xff\xff\xff"
+#define TRANS "\x00\x00\x00\x00"
+
+static uint8_t* k2x2[] = {
+    (uint8_t*)WHITE WHITE,
+    (uint8_t*)WHITE WHITE,
+};
+
+static uint8_t* kMixedNeutralColor3x3[] = {
+    (uint8_t*)WHITE BLACK TRANS,
+    (uint8_t*)TRANS RED TRANS,
+    (uint8_t*)WHITE WHITE WHITE,
+};
+
+static uint8_t* kTransparentNeutralColor3x3[] = {
+    (uint8_t*)TRANS BLACK TRANS,
+    (uint8_t*)BLACK RED BLACK,
+    (uint8_t*)TRANS BLACK TRANS,
+};
+
+static uint8_t* kSingleStretch7x6[] = {
+    (uint8_t*)WHITE WHITE BLACK BLACK BLACK WHITE WHITE,
+    (uint8_t*)WHITE RED RED RED RED RED WHITE,
+    (uint8_t*)BLACK RED RED RED RED RED WHITE,
+    (uint8_t*)BLACK RED RED RED RED RED WHITE,
+    (uint8_t*)WHITE RED RED RED RED RED WHITE,
+    (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
+};
+
+static uint8_t* kMultipleStretch10x7[] = {
+    (uint8_t*)WHITE WHITE BLACK WHITE BLACK BLACK WHITE BLACK WHITE WHITE,
+    (uint8_t*)BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
+    (uint8_t*)BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
+    (uint8_t*)WHITE RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
+    (uint8_t*)BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
+    (uint8_t*)BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
+    (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
+};
+
+static uint8_t* kPadding6x5[] = {
+    (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE, (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE,
+    (uint8_t*)WHITE WHITE WHITE WHITE WHITE BLACK, (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE,
+    (uint8_t*)WHITE WHITE BLACK BLACK WHITE WHITE,
+};
+
+static uint8_t* kLayoutBoundsWrongEdge3x3[] = {
+    (uint8_t*)WHITE RED WHITE,
+    (uint8_t*)RED WHITE WHITE,
+    (uint8_t*)WHITE WHITE WHITE,
+};
+
+static uint8_t* kLayoutBoundsNotEdgeAligned5x5[] = {
+    (uint8_t*)WHITE WHITE WHITE WHITE WHITE, (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+    (uint8_t*)WHITE WHITE WHITE WHITE RED,   (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+    (uint8_t*)WHITE WHITE RED WHITE WHITE,
+};
+
+static uint8_t* kLayoutBounds5x5[] = {
+    (uint8_t*)WHITE WHITE WHITE WHITE WHITE, (uint8_t*)WHITE WHITE WHITE WHITE RED,
+    (uint8_t*)WHITE WHITE WHITE WHITE WHITE, (uint8_t*)WHITE WHITE WHITE WHITE RED,
+    (uint8_t*)WHITE RED WHITE RED WHITE,
+};
+
+static uint8_t* kAsymmetricLayoutBounds5x5[] = {
+    (uint8_t*)WHITE WHITE WHITE WHITE WHITE, (uint8_t*)WHITE WHITE WHITE WHITE RED,
+    (uint8_t*)WHITE WHITE WHITE WHITE WHITE, (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+    (uint8_t*)WHITE RED WHITE WHITE WHITE,
+};
+
+static uint8_t* kPaddingAndLayoutBounds5x5[] = {
+    (uint8_t*)WHITE WHITE WHITE WHITE WHITE, (uint8_t*)WHITE WHITE WHITE WHITE RED,
+    (uint8_t*)WHITE WHITE WHITE WHITE BLACK, (uint8_t*)WHITE WHITE WHITE WHITE RED,
+    (uint8_t*)WHITE RED BLACK RED WHITE,
+};
+
+static uint8_t* kColorfulImage5x5[] = {
+    (uint8_t*)WHITE BLACK WHITE BLACK WHITE, (uint8_t*)BLACK RED BLUE GREEN WHITE,
+    (uint8_t*)BLACK RED GREEN GREEN WHITE,   (uint8_t*)WHITE TRANS BLUE GREEN WHITE,
+    (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+};
+
+static uint8_t* kOutlineOpaque10x10[] = {
+    (uint8_t*)WHITE BLACK BLACK BLACK BLACK BLACK BLACK BLACK BLACK WHITE,
+    (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
+    (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
+    (uint8_t*)WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE,
+    (uint8_t*)WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE,
+    (uint8_t*)WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE,
+    (uint8_t*)WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE,
+    (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
+    (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
+    (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
+};
+
+static uint8_t* kOutlineTranslucent10x10[] = {
+    (uint8_t*)WHITE BLACK BLACK BLACK BLACK BLACK BLACK BLACK BLACK WHITE,
+    (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
+    (uint8_t*)WHITE TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE,
+    (uint8_t*)WHITE TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE,
+    (uint8_t*)WHITE TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE,
+    (uint8_t*)WHITE TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE,
+    (uint8_t*)WHITE TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE,
+    (uint8_t*)WHITE TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE,
+    (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
+    (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
+};
+
+static uint8_t* kOutlineOffsetTranslucent12x10[] = {
+    (uint8_t*)WHITE WHITE WHITE BLACK BLACK BLACK BLACK BLACK BLACK BLACK BLACK WHITE,
+    (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
+    (uint8_t*)WHITE TRANS TRANS TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE,
+    (uint8_t*)WHITE TRANS TRANS TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE,
+    (uint8_t*)WHITE TRANS TRANS TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE,
+    (uint8_t*)WHITE TRANS TRANS TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE,
+    (uint8_t*)WHITE TRANS TRANS TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE,
+    (uint8_t*)WHITE TRANS TRANS TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE,
+    (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
+    (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
+};
+
+static uint8_t* kOutlineRadius5x5[] = {
+    (uint8_t*)WHITE BLACK BLACK BLACK WHITE, (uint8_t*)BLACK TRANS GREEN TRANS WHITE,
+    (uint8_t*)BLACK GREEN GREEN GREEN WHITE, (uint8_t*)BLACK TRANS GREEN TRANS WHITE,
+    (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+};
+
+static uint8_t* kStretchAndPadding5x5[] = {
+    (uint8_t*)WHITE WHITE BLACK WHITE WHITE, (uint8_t*)WHITE RED RED RED WHITE,
+    (uint8_t*)BLACK RED RED RED BLACK,       (uint8_t*)WHITE RED RED RED WHITE,
+    (uint8_t*)WHITE WHITE BLACK WHITE WHITE,
+};
+
+TEST(NinePatchTest, Minimum3x3) {
+  std::string err;
+  EXPECT_EQ(nullptr, NinePatch::Create(k2x2, 2, 2, &err));
+  EXPECT_FALSE(err.empty());
+}
+
+TEST(NinePatchTest, MixedNeutralColors) {
+  std::string err;
+  EXPECT_EQ(nullptr, NinePatch::Create(kMixedNeutralColor3x3, 3, 3, &err));
+  EXPECT_FALSE(err.empty());
+}
+
+TEST(NinePatchTest, TransparentNeutralColor) {
+  std::string err;
+  EXPECT_NE(nullptr, NinePatch::Create(kTransparentNeutralColor3x3, 3, 3, &err));
+}
+
+TEST(NinePatchTest, SingleStretchRegion) {
+  std::string err;
+  std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(kSingleStretch7x6, 7, 6, &err);
+  ASSERT_NE(nullptr, nine_patch);
+
+  ASSERT_EQ(1u, nine_patch->horizontal_stretch_regions.size());
+  ASSERT_EQ(1u, nine_patch->vertical_stretch_regions.size());
+
+  EXPECT_EQ(Range(1, 4), nine_patch->horizontal_stretch_regions.front());
+  EXPECT_EQ(Range(1, 3), nine_patch->vertical_stretch_regions.front());
+}
+
+TEST(NinePatchTest, MultipleStretchRegions) {
+  std::string err;
+  std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(kMultipleStretch10x7, 10, 7, &err);
+  ASSERT_NE(nullptr, nine_patch);
+
+  ASSERT_EQ(3u, nine_patch->horizontal_stretch_regions.size());
+  ASSERT_EQ(2u, nine_patch->vertical_stretch_regions.size());
+
+  EXPECT_EQ(Range(1, 2), nine_patch->horizontal_stretch_regions[0]);
+  EXPECT_EQ(Range(3, 5), nine_patch->horizontal_stretch_regions[1]);
+  EXPECT_EQ(Range(6, 7), nine_patch->horizontal_stretch_regions[2]);
+
+  EXPECT_EQ(Range(0, 2), nine_patch->vertical_stretch_regions[0]);
+  EXPECT_EQ(Range(3, 5), nine_patch->vertical_stretch_regions[1]);
+}
+
+TEST(NinePatchTest, InferPaddingFromStretchRegions) {
+  std::string err;
+  std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(kMultipleStretch10x7, 10, 7, &err);
+  ASSERT_NE(nullptr, nine_patch);
+  EXPECT_EQ(Bounds(1, 0, 1, 0), nine_patch->padding);
+}
+
+TEST(NinePatchTest, Padding) {
+  std::string err;
+  std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(kPadding6x5, 6, 5, &err);
+  ASSERT_NE(nullptr, nine_patch);
+  EXPECT_EQ(Bounds(1, 1, 1, 1), nine_patch->padding);
+}
+
+TEST(NinePatchTest, LayoutBoundsAreOnWrongEdge) {
+  std::string err;
+  EXPECT_EQ(nullptr, NinePatch::Create(kLayoutBoundsWrongEdge3x3, 3, 3, &err));
+  EXPECT_FALSE(err.empty());
+}
+
+TEST(NinePatchTest, LayoutBoundsMustTouchEdges) {
+  std::string err;
+  EXPECT_EQ(nullptr, NinePatch::Create(kLayoutBoundsNotEdgeAligned5x5, 5, 5, &err));
+  EXPECT_FALSE(err.empty());
+}
+
+TEST(NinePatchTest, LayoutBounds) {
+  std::string err;
+  std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(kLayoutBounds5x5, 5, 5, &err);
+  ASSERT_NE(nullptr, nine_patch);
+  EXPECT_EQ(Bounds(1, 1, 1, 1), nine_patch->layout_bounds);
+
+  nine_patch = NinePatch::Create(kAsymmetricLayoutBounds5x5, 5, 5, &err);
+  ASSERT_NE(nullptr, nine_patch);
+  EXPECT_EQ(Bounds(1, 1, 0, 0), nine_patch->layout_bounds);
+}
+
+TEST(NinePatchTest, PaddingAndLayoutBounds) {
+  std::string err;
+  std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(kPaddingAndLayoutBounds5x5, 5, 5, &err);
+  ASSERT_NE(nullptr, nine_patch);
+  EXPECT_EQ(Bounds(1, 1, 1, 1), nine_patch->padding);
+  EXPECT_EQ(Bounds(1, 1, 1, 1), nine_patch->layout_bounds);
+}
+
+TEST(NinePatchTest, RegionColorsAreCorrect) {
+  std::string err;
+  std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(kColorfulImage5x5, 5, 5, &err);
+  ASSERT_NE(nullptr, nine_patch);
+
+  std::vector<uint32_t> expected_colors = {
+      NinePatch::PackRGBA((uint8_t*)RED),   (uint32_t)android::Res_png_9patch::NO_COLOR,
+      NinePatch::PackRGBA((uint8_t*)GREEN), (uint32_t)android::Res_png_9patch::TRANSPARENT_COLOR,
+      NinePatch::PackRGBA((uint8_t*)BLUE),  NinePatch::PackRGBA((uint8_t*)GREEN),
+  };
+  EXPECT_EQ(expected_colors, nine_patch->region_colors);
+}
+
+TEST(NinePatchTest, OutlineFromOpaqueImage) {
+  std::string err;
+  std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(kOutlineOpaque10x10, 10, 10, &err);
+  ASSERT_NE(nullptr, nine_patch);
+  EXPECT_EQ(Bounds(2, 2, 2, 2), nine_patch->outline);
+  EXPECT_EQ(0x000000ffu, nine_patch->outline_alpha);
+  EXPECT_EQ(0.0f, nine_patch->outline_radius);
+}
+
+TEST(NinePatchTest, OutlineFromTranslucentImage) {
+  std::string err;
+  std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(kOutlineTranslucent10x10, 10, 10, &err);
+  ASSERT_NE(nullptr, nine_patch);
+  EXPECT_EQ(Bounds(3, 3, 3, 3), nine_patch->outline);
+  EXPECT_EQ(0x000000b3u, nine_patch->outline_alpha);
+  EXPECT_EQ(0.0f, nine_patch->outline_radius);
+}
+
+TEST(NinePatchTest, OutlineFromOffCenterImage) {
+  std::string err;
+  std::unique_ptr<NinePatch> nine_patch =
+      NinePatch::Create(kOutlineOffsetTranslucent12x10, 12, 10, &err);
+  ASSERT_NE(nullptr, nine_patch);
+
+  // TODO(adamlesinski): The old AAPT algorithm searches from the outside to the
+  // middle for each inset. If the outline is shifted, the search may not find a
+  // closer bounds.
+  // This check should be:
+  //   EXPECT_EQ(Bounds(5, 3, 3, 3), ninePatch->outline);
+  // but until I know what behavior I'm breaking, I will leave it at the
+  // incorrect:
+  EXPECT_EQ(Bounds(4, 3, 3, 3), nine_patch->outline);
+
+  EXPECT_EQ(0x000000b3u, nine_patch->outline_alpha);
+  EXPECT_EQ(0.0f, nine_patch->outline_radius);
+}
+
+TEST(NinePatchTest, OutlineRadius) {
+  std::string err;
+  std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(kOutlineRadius5x5, 5, 5, &err);
+  ASSERT_NE(nullptr, nine_patch);
+  EXPECT_EQ(Bounds(0, 0, 0, 0), nine_patch->outline);
+  EXPECT_EQ(3.4142f, nine_patch->outline_radius);
+}
+
+::testing::AssertionResult BigEndianOne(uint8_t* cursor) {
+  if (cursor[0] == 0 && cursor[1] == 0 && cursor[2] == 0 && cursor[3] == 1) {
+    return ::testing::AssertionSuccess();
+  }
+  return ::testing::AssertionFailure() << "Not BigEndian 1";
+}
+
+TEST(NinePatchTest, SerializePngEndianness) {
+  std::string err;
+  std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(kStretchAndPadding5x5, 5, 5, &err);
+  ASSERT_NE(nullptr, nine_patch);
+
+  size_t len;
+  std::unique_ptr<uint8_t[]> data = nine_patch->SerializeBase(&len);
+  ASSERT_NE(nullptr, data);
+  ASSERT_NE(0u, len);
+
+  // Skip past wasDeserialized + numXDivs + numYDivs + numColors + xDivsOffset +
+  // yDivsOffset
+  // (12 bytes)
+  uint8_t* cursor = data.get() + 12;
+
+  // Check that padding is big-endian. Expecting value 1.
+  EXPECT_TRUE(BigEndianOne(cursor));
+  EXPECT_TRUE(BigEndianOne(cursor + 4));
+  EXPECT_TRUE(BigEndianOne(cursor + 8));
+  EXPECT_TRUE(BigEndianOne(cursor + 12));
+}
+
+}  // namespace android