| /* | |
| * 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 |