diff options
| author | 2023-11-30 04:21:19 +0000 | |
|---|---|---|
| committer | 2023-11-30 04:21:19 +0000 | |
| commit | d067918b8705c65438f92279cd5a8e8fdbccd6b7 (patch) | |
| tree | c4fa2b05024bb6b03fae82747e9552467c062de4 /libs/androidfw | |
| parent | 2f16184f65f5d9739645f33001f297b9eddf06ec (diff) | |
| parent | 917043bc2586743afda5a21386893fa8c787800b (diff) | |
Merge "Revert "Move some image/9patch code to androidfw"" into main
Diffstat (limited to 'libs/androidfw')
| -rw-r--r-- | libs/androidfw/Android.bp | 13 | ||||
| -rw-r--r-- | libs/androidfw/BigBufferStream.cpp | 129 | ||||
| -rw-r--r-- | libs/androidfw/FileStream.cpp | 205 | ||||
| -rw-r--r-- | libs/androidfw/NinePatch.cpp | 682 | ||||
| -rw-r--r-- | libs/androidfw/Png.cpp | 1259 | ||||
| -rw-r--r-- | libs/androidfw/PngChunkFilter.cpp | 176 | ||||
| -rw-r--r-- | libs/androidfw/PngCrunch.cpp | 730 | ||||
| -rw-r--r-- | libs/androidfw/include/androidfw/BigBufferStream.h | 76 | ||||
| -rw-r--r-- | libs/androidfw/include/androidfw/FileStream.h | 103 | ||||
| -rw-r--r-- | libs/androidfw/include/androidfw/Image.h | 207 | ||||
| -rw-r--r-- | libs/androidfw/include/androidfw/Png.h | 100 | ||||
| -rw-r--r-- | libs/androidfw/include/androidfw/Streams.h | 109 | ||||
| -rw-r--r-- | libs/androidfw/tests/FileStream_test.cpp | 127 | ||||
| -rw-r--r-- | libs/androidfw/tests/NinePatch_test.cpp | 341 |
14 files changed, 1 insertions, 4256 deletions
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp index 2f28363aedc7..47a7f3579764 100644 --- a/libs/androidfw/Android.bp +++ b/libs/androidfw/Android.bp @@ -63,21 +63,15 @@ cc_library { "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", @@ -90,10 +84,7 @@ cc_library { ], export_include_dirs: ["include"], export_shared_lib_headers: ["libz"], - static_libs: [ - "libincfs-utils", - "libpng", - ], + static_libs: ["libincfs-utils"], whole_static_libs: [ "libandroidfw_pathutils", "libincfs-utils", @@ -207,11 +198,9 @@ cc_test { "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 deleted file mode 100644 index f18199cfa52b..000000000000 --- a/libs/androidfw/BigBufferStream.cpp +++ /dev/null @@ -1,129 +0,0 @@ -/* - * 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 deleted file mode 100644 index b86c9cb729d4..000000000000 --- a/libs/androidfw/FileStream.cpp +++ /dev/null @@ -1,205 +0,0 @@ -/* - * 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 deleted file mode 100644 index 1fdbebfb6daa..000000000000 --- a/libs/androidfw/NinePatch.cpp +++ /dev/null @@ -1,682 +0,0 @@ -/* - * 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 deleted file mode 100644 index fb45cd9b49d0..000000000000 --- a/libs/androidfw/Png.cpp +++ /dev/null @@ -1,1259 +0,0 @@ -/* - * 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 deleted file mode 100644 index 331b94803e93..000000000000 --- a/libs/androidfw/PngChunkFilter.cpp +++ /dev/null @@ -1,176 +0,0 @@ -/* - * 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 deleted file mode 100644 index cf3c0eeff402..000000000000 --- a/libs/androidfw/PngCrunch.cpp +++ /dev/null @@ -1,730 +0,0 @@ -/* - * 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 deleted file mode 100644 index e55fe0d653cc..000000000000 --- a/libs/androidfw/include/androidfw/BigBufferStream.h +++ /dev/null @@ -1,76 +0,0 @@ -/* - * 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 deleted file mode 100644 index fb84a91a00de..000000000000 --- a/libs/androidfw/include/androidfw/FileStream.h +++ /dev/null @@ -1,103 +0,0 @@ -/* - * 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 deleted file mode 100644 index c18c34c25bf9..000000000000 --- a/libs/androidfw/include/androidfw/Image.h +++ /dev/null @@ -1,207 +0,0 @@ -/* - * 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 deleted file mode 100644 index 2ece43e08110..000000000000 --- a/libs/androidfw/include/androidfw/Png.h +++ /dev/null @@ -1,100 +0,0 @@ -/* - * 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 deleted file mode 100644 index 89f16205162a..000000000000 --- a/libs/androidfw/include/androidfw/Streams.h +++ /dev/null @@ -1,109 +0,0 @@ -/* - * 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> - -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 deleted file mode 100644 index 978597507a6d..000000000000 --- a/libs/androidfw/tests/FileStream_test.cpp +++ /dev/null @@ -1,127 +0,0 @@ -/* - * 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 deleted file mode 100644 index 7ee8e9ebd624..000000000000 --- a/libs/androidfw/tests/NinePatch_test.cpp +++ /dev/null @@ -1,341 +0,0 @@ -/* - * 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 |