blob: 1fdbebfb6daace1c23001520e05b922ee973f771 [file] [log] [blame]
Adam Lesinski21efb682016-09-14 17:35:43 -07001/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Adam Lesinski21efb682016-09-14 17:35:43 -070017#include <sstream>
18#include <string>
19#include <vector>
20
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +000021#include "androidfw/Image.h"
Adam Lesinskice5e56e22016-10-21 17:56:45 -070022#include "androidfw/ResourceTypes.h"
Adam Lesinskid5083f62017-01-16 15:07:21 -080023#include "androidfw/StringPiece.h"
Adam Lesinskice5e56e22016-10-21 17:56:45 -070024
Adam Lesinskid5083f62017-01-16 15:07:21 -080025using android::StringPiece;
26
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +000027namespace android {
Adam Lesinski21efb682016-09-14 17:35:43 -070028
29// Colors in the format 0xAARRGGBB (the way 9-patch expects it).
30constexpr static const uint32_t kColorOpaqueWhite = 0xffffffffu;
31constexpr static const uint32_t kColorOpaqueBlack = 0xff000000u;
Adam Lesinskicacb28f2016-10-19 12:18:14 -070032constexpr static const uint32_t kColorOpaqueRed = 0xffff0000u;
Adam Lesinski21efb682016-09-14 17:35:43 -070033
34constexpr static const uint32_t kPrimaryColor = kColorOpaqueBlack;
35constexpr static const uint32_t kSecondaryColor = kColorOpaqueRed;
36
37/**
38 * Returns the alpha value encoded in the 0xAARRGBB encoded pixel.
39 */
Adam Lesinskice5e56e22016-10-21 17:56:45 -070040static uint32_t get_alpha(uint32_t color);
Adam Lesinski21efb682016-09-14 17:35:43 -070041
42/**
43 * Determines whether a color on an ImageLine is valid.
44 * A 9patch image may use a transparent color as neutral,
45 * or a fully opaque white color as neutral, based on the
46 * pixel color at (0,0) of the image. One or the other is fine,
47 * but we need to ensure consistency throughout the image.
48 */
49class ColorValidator {
Adam Lesinskicacb28f2016-10-19 12:18:14 -070050 public:
51 virtual ~ColorValidator() = default;
Adam Lesinski21efb682016-09-14 17:35:43 -070052
Adam Lesinskicacb28f2016-10-19 12:18:14 -070053 /**
54 * Returns true if the color specified is a neutral color
55 * (no padding, stretching, or optical bounds).
56 */
Adam Lesinskice5e56e22016-10-21 17:56:45 -070057 virtual bool IsNeutralColor(uint32_t color) const = 0;
Adam Lesinski21efb682016-09-14 17:35:43 -070058
Adam Lesinskicacb28f2016-10-19 12:18:14 -070059 /**
60 * Returns true if the color is either a neutral color
61 * or one denoting padding, stretching, or optical bounds.
62 */
Adam Lesinskice5e56e22016-10-21 17:56:45 -070063 bool IsValidColor(uint32_t color) const {
Adam Lesinskicacb28f2016-10-19 12:18:14 -070064 switch (color) {
65 case kPrimaryColor:
66 case kSecondaryColor:
67 return true;
Adam Lesinski21efb682016-09-14 17:35:43 -070068 }
Adam Lesinskice5e56e22016-10-21 17:56:45 -070069 return IsNeutralColor(color);
Adam Lesinskicacb28f2016-10-19 12:18:14 -070070 }
Adam Lesinski21efb682016-09-14 17:35:43 -070071};
72
73// Walks an ImageLine and records Ranges of primary and secondary colors.
Adam Lesinskicacb28f2016-10-19 12:18:14 -070074// The primary color is black and is used to denote a padding or stretching
75// range,
Adam Lesinski21efb682016-09-14 17:35:43 -070076// depending on which border we're iterating over.
77// The secondary color is red and is used to denote optical bounds.
78//
Adam Lesinskicacb28f2016-10-19 12:18:14 -070079// An ImageLine is a templated-interface that would look something like this if
80// it
Adam Lesinski21efb682016-09-14 17:35:43 -070081// were polymorphic:
82//
83// class ImageLine {
84// public:
Adam Lesinskice5e56e22016-10-21 17:56:45 -070085// virtual int32_t GetLength() const = 0;
86// virtual uint32_t GetColor(int32_t idx) const = 0;
Adam Lesinski21efb682016-09-14 17:35:43 -070087// };
88//
89template <typename ImageLine>
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +000090static bool FillRanges(const ImageLine* image_line, const ColorValidator* color_validator,
91 std::vector<Range>* primary_ranges, std::vector<Range>* secondary_ranges,
Adam Lesinskice5e56e22016-10-21 17:56:45 -070092 std::string* out_err) {
93 const int32_t length = image_line->GetLength();
Adam Lesinski21efb682016-09-14 17:35:43 -070094
Adam Lesinskice5e56e22016-10-21 17:56:45 -070095 uint32_t last_color = 0xffffffffu;
Adam Lesinskicacb28f2016-10-19 12:18:14 -070096 for (int32_t idx = 1; idx < length - 1; idx++) {
Adam Lesinskice5e56e22016-10-21 17:56:45 -070097 const uint32_t color = image_line->GetColor(idx);
98 if (!color_validator->IsValidColor(color)) {
99 *out_err = "found an invalid color";
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700100 return false;
Adam Lesinski21efb682016-09-14 17:35:43 -0700101 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700102
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700103 if (color != last_color) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700104 // We are ending a range. Which range?
105 // note: encode the x offset without the final 1 pixel border.
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700106 if (last_color == kPrimaryColor) {
107 primary_ranges->back().end = idx - 1;
108 } else if (last_color == kSecondaryColor) {
109 secondary_ranges->back().end = idx - 1;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700110 }
111
112 // We are starting a range. Which range?
113 // note: encode the x offset without the final 1 pixel border.
114 if (color == kPrimaryColor) {
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700115 primary_ranges->push_back(Range(idx - 1, length - 2));
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700116 } else if (color == kSecondaryColor) {
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700117 secondary_ranges->push_back(Range(idx - 1, length - 2));
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700118 }
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700119 last_color = color;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700120 }
121 }
122 return true;
Adam Lesinski21efb682016-09-14 17:35:43 -0700123}
124
125/**
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700126 * Iterates over a row in an image. Implements the templated ImageLine
127 * interface.
Adam Lesinski21efb682016-09-14 17:35:43 -0700128 */
129class HorizontalImageLine {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700130 public:
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000131 explicit HorizontalImageLine(uint8_t** rows, int32_t xoffset, int32_t yoffset, int32_t length)
132 : rows_(rows), xoffset_(xoffset), yoffset_(yoffset), length_(length) {
133 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700134
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000135 inline int32_t GetLength() const {
136 return length_;
137 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700138
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700139 inline uint32_t GetColor(int32_t idx) const {
140 return NinePatch::PackRGBA(rows_[yoffset_] + (idx + xoffset_) * 4);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700141 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700142
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700143 private:
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700144 uint8_t** rows_;
145 int32_t xoffset_, yoffset_, length_;
Adam Lesinski21efb682016-09-14 17:35:43 -0700146
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700147 DISALLOW_COPY_AND_ASSIGN(HorizontalImageLine);
Adam Lesinski21efb682016-09-14 17:35:43 -0700148};
149
150/**
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700151 * Iterates over a column in an image. Implements the templated ImageLine
152 * interface.
Adam Lesinski21efb682016-09-14 17:35:43 -0700153 */
154class VerticalImageLine {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700155 public:
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000156 explicit VerticalImageLine(uint8_t** rows, int32_t xoffset, int32_t yoffset, int32_t length)
157 : rows_(rows), xoffset_(xoffset), yoffset_(yoffset), length_(length) {
158 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700159
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000160 inline int32_t GetLength() const {
161 return length_;
162 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700163
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700164 inline uint32_t GetColor(int32_t idx) const {
165 return NinePatch::PackRGBA(rows_[yoffset_ + idx] + (xoffset_ * 4));
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700166 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700167
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700168 private:
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700169 uint8_t** rows_;
170 int32_t xoffset_, yoffset_, length_;
Adam Lesinski21efb682016-09-14 17:35:43 -0700171
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700172 DISALLOW_COPY_AND_ASSIGN(VerticalImageLine);
Adam Lesinski21efb682016-09-14 17:35:43 -0700173};
174
175class DiagonalImageLine {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700176 public:
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000177 explicit DiagonalImageLine(uint8_t** rows, int32_t xoffset, int32_t yoffset, int32_t xstep,
178 int32_t ystep, int32_t length)
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700179 : rows_(rows),
180 xoffset_(xoffset),
181 yoffset_(yoffset),
182 xstep_(xstep),
183 ystep_(ystep),
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000184 length_(length) {
185 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700186
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000187 inline int32_t GetLength() const {
188 return length_;
189 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700190
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700191 inline uint32_t GetColor(int32_t idx) const {
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000192 return NinePatch::PackRGBA(rows_[yoffset_ + (idx * ystep_)] + ((idx + xoffset_) * xstep_) * 4);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700193 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700194
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700195 private:
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700196 uint8_t** rows_;
197 int32_t xoffset_, yoffset_, xstep_, ystep_, length_;
Adam Lesinski21efb682016-09-14 17:35:43 -0700198
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700199 DISALLOW_COPY_AND_ASSIGN(DiagonalImageLine);
Adam Lesinski21efb682016-09-14 17:35:43 -0700200};
201
202class TransparentNeutralColorValidator : public ColorValidator {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700203 public:
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700204 bool IsNeutralColor(uint32_t color) const override {
205 return get_alpha(color) == 0;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700206 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700207};
208
209class WhiteNeutralColorValidator : public ColorValidator {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700210 public:
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700211 bool IsNeutralColor(uint32_t color) const override {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700212 return color == kColorOpaqueWhite;
213 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700214};
215
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700216inline static uint32_t get_alpha(uint32_t color) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700217 return (color & 0xff000000u) >> 24;
Adam Lesinski21efb682016-09-14 17:35:43 -0700218}
219
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700220static bool PopulateBounds(const std::vector<Range>& padding,
221 const std::vector<Range>& layout_bounds,
Yurii Zubrytskyia5775142022-11-02 17:49:49 -0700222 const std::vector<Range>& stretch_regions, const int32_t length,
223 int32_t* padding_start, int32_t* padding_end, int32_t* layout_start,
224 int32_t* layout_end, StringPiece edge_name, std::string* out_err) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700225 if (padding.size() > 1) {
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700226 std::stringstream err_stream;
227 err_stream << "too many padding sections on " << edge_name << " border";
228 *out_err = err_stream.str();
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700229 return false;
230 }
231
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700232 *padding_start = 0;
233 *padding_end = 0;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700234 if (!padding.empty()) {
235 const Range& range = padding.front();
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700236 *padding_start = range.start;
237 *padding_end = length - range.end;
238 } else if (!stretch_regions.empty()) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700239 // No padding was defined. Compute the padding from the first and last
240 // stretch regions.
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700241 *padding_start = stretch_regions.front().start;
242 *padding_end = length - stretch_regions.back().end;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700243 }
244
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700245 if (layout_bounds.size() > 2) {
246 std::stringstream err_stream;
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000247 err_stream << "too many layout bounds sections on " << edge_name << " border";
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700248 *out_err = err_stream.str();
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700249 return false;
250 }
251
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700252 *layout_start = 0;
253 *layout_end = 0;
254 if (layout_bounds.size() >= 1) {
255 const Range& range = layout_bounds.front();
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700256 // If there is only one layout bound segment, it might not start at 0, but
257 // then it should
258 // end at length.
259 if (range.start != 0 && range.end != length) {
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700260 std::stringstream err_stream;
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000261 err_stream << "layout bounds on " << edge_name << " border must start at edge";
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700262 *out_err = err_stream.str();
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700263 return false;
264 }
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700265 *layout_start = range.end;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700266
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700267 if (layout_bounds.size() >= 2) {
268 const Range& range = layout_bounds.back();
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700269 if (range.end != length) {
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700270 std::stringstream err_stream;
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000271 err_stream << "layout bounds on " << edge_name << " border must start at edge";
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700272 *out_err = err_stream.str();
Adam Lesinski21efb682016-09-14 17:35:43 -0700273 return false;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700274 }
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700275 *layout_end = length - range.start;
Adam Lesinski21efb682016-09-14 17:35:43 -0700276 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700277 }
278 return true;
Adam Lesinski21efb682016-09-14 17:35:43 -0700279}
280
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000281static int32_t CalculateSegmentCount(const std::vector<Range>& stretch_regions, int32_t length) {
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700282 if (stretch_regions.size() == 0) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700283 return 0;
284 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700285
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700286 const bool start_is_fixed = stretch_regions.front().start != 0;
287 const bool end_is_fixed = stretch_regions.back().end != length;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700288 int32_t modifier = 0;
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700289 if (start_is_fixed && end_is_fixed) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700290 modifier = 1;
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700291 } else if (!start_is_fixed && !end_is_fixed) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700292 modifier = -1;
293 }
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700294 return static_cast<int32_t>(stretch_regions.size()) * 2 + modifier;
Adam Lesinski21efb682016-09-14 17:35:43 -0700295}
296
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700297static uint32_t GetRegionColor(uint8_t** rows, const Bounds& region) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700298 // Sample the first pixel to compare against.
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000299 const uint32_t expected_color = NinePatch::PackRGBA(rows[region.top] + region.left * 4);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700300 for (int32_t y = region.top; y < region.bottom; y++) {
301 const uint8_t* row = rows[y];
302 for (int32_t x = region.left; x < region.right; x++) {
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700303 const uint32_t color = NinePatch::PackRGBA(row + x * 4);
304 if (get_alpha(color) == 0) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700305 // The color is transparent.
306 // If the expectedColor is not transparent, NO_COLOR.
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700307 if (get_alpha(expected_color) != 0) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700308 return android::Res_png_9patch::NO_COLOR;
Adam Lesinski21efb682016-09-14 17:35:43 -0700309 }
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700310 } else if (color != expected_color) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700311 return android::Res_png_9patch::NO_COLOR;
312 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700313 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700314 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700315
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700316 if (get_alpha(expected_color) == 0) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700317 return android::Res_png_9patch::TRANSPARENT_COLOR;
318 }
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700319 return expected_color;
Adam Lesinski21efb682016-09-14 17:35:43 -0700320}
321
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700322// Fills out_colors with each 9-patch section's color. If the whole section is
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700323// transparent,
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700324// it gets the special TRANSPARENT color. If the whole section is the same
325// color, it is assigned
326// that color. Otherwise it gets the special NO_COLOR color.
Adam Lesinski21efb682016-09-14 17:35:43 -0700327//
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700328// Note that the rows contain the 9-patch 1px border, and the indices in the
329// stretch regions are
330// already offset to exclude the border. This means that each time the rows are
331// accessed,
Adam Lesinski21efb682016-09-14 17:35:43 -0700332// the indices must be offset by 1.
333//
334// width and height also include the 9-patch 1px border.
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000335static void CalculateRegionColors(uint8_t** rows,
336 const std::vector<Range>& horizontal_stretch_regions,
337 const std::vector<Range>& vertical_stretch_regions,
338 const int32_t width, const int32_t height,
339 std::vector<uint32_t>* out_colors) {
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700340 int32_t next_top = 0;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700341 Bounds bounds;
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700342 auto row_iter = vertical_stretch_regions.begin();
343 while (next_top != height) {
344 if (row_iter != vertical_stretch_regions.end()) {
345 if (next_top != row_iter->start) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700346 // This is a fixed segment.
347 // Offset the bounds by 1 to accommodate the border.
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700348 bounds.top = next_top + 1;
349 bounds.bottom = row_iter->start + 1;
350 next_top = row_iter->start;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700351 } else {
352 // This is a stretchy segment.
353 // Offset the bounds by 1 to accommodate the border.
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700354 bounds.top = row_iter->start + 1;
355 bounds.bottom = row_iter->end + 1;
356 next_top = row_iter->end;
357 ++row_iter;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700358 }
359 } else {
360 // This is the end, fixed section.
361 // Offset the bounds by 1 to accommodate the border.
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700362 bounds.top = next_top + 1;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700363 bounds.bottom = height + 1;
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700364 next_top = height;
Adam Lesinski21efb682016-09-14 17:35:43 -0700365 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700366
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700367 int32_t next_left = 0;
368 auto col_iter = horizontal_stretch_regions.begin();
369 while (next_left != width) {
370 if (col_iter != horizontal_stretch_regions.end()) {
371 if (next_left != col_iter->start) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700372 // This is a fixed segment.
373 // Offset the bounds by 1 to accommodate the border.
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700374 bounds.left = next_left + 1;
375 bounds.right = col_iter->start + 1;
376 next_left = col_iter->start;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700377 } else {
378 // This is a stretchy segment.
379 // Offset the bounds by 1 to accommodate the border.
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700380 bounds.left = col_iter->start + 1;
381 bounds.right = col_iter->end + 1;
382 next_left = col_iter->end;
383 ++col_iter;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700384 }
385 } else {
386 // This is the end, fixed section.
387 // Offset the bounds by 1 to accommodate the border.
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700388 bounds.left = next_left + 1;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700389 bounds.right = width + 1;
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700390 next_left = width;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700391 }
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700392 out_colors->push_back(GetRegionColor(rows, bounds));
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700393 }
394 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700395}
396
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700397// Calculates the insets of a row/column of pixels based on where the largest
398// alpha value begins
Adam Lesinski21efb682016-09-14 17:35:43 -0700399// (on both sides).
400template <typename ImageLine>
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000401static void FindOutlineInsets(const ImageLine* image_line, int32_t* out_start, int32_t* out_end) {
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700402 *out_start = 0;
403 *out_end = 0;
Adam Lesinski21efb682016-09-14 17:35:43 -0700404
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700405 const int32_t length = image_line->GetLength();
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700406 if (length < 3) {
Adam Lesinski21efb682016-09-14 17:35:43 -0700407 return;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700408 }
409
410 // If the length is odd, we want both sides to process the center pixel,
411 // so we use two different midpoints (to account for < and <= in the different
412 // loops).
413 const int32_t mid2 = length / 2;
414 const int32_t mid1 = mid2 + (length % 2);
415
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700416 uint32_t max_alpha = 0;
417 for (int32_t i = 0; i < mid1 && max_alpha != 0xff; i++) {
418 uint32_t alpha = get_alpha(image_line->GetColor(i));
419 if (alpha > max_alpha) {
420 max_alpha = alpha;
421 *out_start = i;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700422 }
423 }
424
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700425 max_alpha = 0;
426 for (int32_t i = length - 1; i >= mid2 && max_alpha != 0xff; i--) {
427 uint32_t alpha = get_alpha(image_line->GetColor(i));
428 if (alpha > max_alpha) {
429 max_alpha = alpha;
430 *out_end = length - (i + 1);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700431 }
432 }
433 return;
Adam Lesinski21efb682016-09-14 17:35:43 -0700434}
435
436template <typename ImageLine>
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700437static uint32_t FindMaxAlpha(const ImageLine* image_line) {
438 const int32_t length = image_line->GetLength();
439 uint32_t max_alpha = 0;
440 for (int32_t idx = 0; idx < length && max_alpha != 0xff; idx++) {
441 uint32_t alpha = get_alpha(image_line->GetColor(idx));
442 if (alpha > max_alpha) {
443 max_alpha = alpha;
Adam Lesinski21efb682016-09-14 17:35:43 -0700444 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700445 }
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700446 return max_alpha;
Adam Lesinski21efb682016-09-14 17:35:43 -0700447}
448
449// Pack the pixels in as 0xAARRGGBB (as 9-patch expects it).
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700450uint32_t NinePatch::PackRGBA(const uint8_t* pixel) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700451 return (pixel[3] << 24) | (pixel[0] << 16) | (pixel[1] << 8) | pixel[2];
Adam Lesinski21efb682016-09-14 17:35:43 -0700452}
453
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000454std::unique_ptr<NinePatch> NinePatch::Create(uint8_t** rows, const int32_t width,
455 const int32_t height, std::string* out_err) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700456 if (width < 3 || height < 3) {
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700457 *out_err = "image must be at least 3x3 (1x1 image with 1 pixel border)";
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700458 return {};
459 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700460
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700461 std::vector<Range> horizontal_padding;
462 std::vector<Range> horizontal_layout_bounds;
463 std::vector<Range> vertical_padding;
464 std::vector<Range> vertical_layout_bounds;
465 std::vector<Range> unexpected_ranges;
466 std::unique_ptr<ColorValidator> color_validator;
Adam Lesinski21efb682016-09-14 17:35:43 -0700467
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700468 if (rows[0][3] == 0) {
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000469 color_validator = std::make_unique<TransparentNeutralColorValidator>();
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700470 } else if (PackRGBA(rows[0]) == kColorOpaqueWhite) {
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000471 color_validator = std::make_unique<WhiteNeutralColorValidator>();
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700472 } else {
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000473 *out_err = "top-left corner pixel must be either opaque white or transparent";
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700474 return {};
475 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700476
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700477 // Private constructor, can't use make_unique.
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700478 auto nine_patch = std::unique_ptr<NinePatch>(new NinePatch());
Adam Lesinski21efb682016-09-14 17:35:43 -0700479
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700480 HorizontalImageLine top_row(rows, 0, 0, width);
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000481 if (!FillRanges(&top_row, color_validator.get(), &nine_patch->horizontal_stretch_regions,
482 &unexpected_ranges, out_err)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700483 return {};
484 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700485
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700486 if (!unexpected_ranges.empty()) {
487 const Range& range = unexpected_ranges[0];
488 std::stringstream err_stream;
489 err_stream << "found unexpected optical bounds (red pixel) on top border "
490 << "at x=" << range.start + 1;
491 *out_err = err_stream.str();
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700492 return {};
493 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700494
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700495 VerticalImageLine left_col(rows, 0, 0, height);
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000496 if (!FillRanges(&left_col, color_validator.get(), &nine_patch->vertical_stretch_regions,
497 &unexpected_ranges, out_err)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700498 return {};
499 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700500
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700501 if (!unexpected_ranges.empty()) {
502 const Range& range = unexpected_ranges[0];
503 std::stringstream err_stream;
504 err_stream << "found unexpected optical bounds (red pixel) on left border "
505 << "at y=" << range.start + 1;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700506 return {};
507 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700508
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700509 HorizontalImageLine bottom_row(rows, 0, height - 1, width);
510 if (!FillRanges(&bottom_row, color_validator.get(), &horizontal_padding,
511 &horizontal_layout_bounds, out_err)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700512 return {};
513 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700514
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700515 if (!PopulateBounds(horizontal_padding, horizontal_layout_bounds,
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000516 nine_patch->horizontal_stretch_regions, width - 2, &nine_patch->padding.left,
517 &nine_patch->padding.right, &nine_patch->layout_bounds.left,
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700518 &nine_patch->layout_bounds.right, "bottom", out_err)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700519 return {};
520 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700521
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700522 VerticalImageLine right_col(rows, width - 1, 0, height);
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000523 if (!FillRanges(&right_col, color_validator.get(), &vertical_padding, &vertical_layout_bounds,
524 out_err)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700525 return {};
526 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700527
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700528 if (!PopulateBounds(vertical_padding, vertical_layout_bounds,
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000529 nine_patch->vertical_stretch_regions, height - 2, &nine_patch->padding.top,
530 &nine_patch->padding.bottom, &nine_patch->layout_bounds.top,
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700531 &nine_patch->layout_bounds.bottom, "right", out_err)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700532 return {};
533 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700534
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700535 // Fill the region colors of the 9-patch.
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000536 const int32_t num_rows = CalculateSegmentCount(nine_patch->horizontal_stretch_regions, width - 2);
537 const int32_t num_cols = CalculateSegmentCount(nine_patch->vertical_stretch_regions, height - 2);
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700538 if ((int64_t)num_rows * (int64_t)num_cols > 0x7f) {
539 *out_err = "too many regions in 9-patch";
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700540 return {};
541 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700542
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700543 nine_patch->region_colors.reserve(num_rows * num_cols);
544 CalculateRegionColors(rows, nine_patch->horizontal_stretch_regions,
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000545 nine_patch->vertical_stretch_regions, width - 2, height - 2,
546 &nine_patch->region_colors);
Adam Lesinski21efb682016-09-14 17:35:43 -0700547
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700548 // Compute the outline based on opacity.
Adam Lesinski21efb682016-09-14 17:35:43 -0700549
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700550 // Find left and right extent of 9-patch content on center row.
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700551 HorizontalImageLine mid_row(rows, 1, height / 2, width - 2);
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000552 FindOutlineInsets(&mid_row, &nine_patch->outline.left, &nine_patch->outline.right);
Adam Lesinski21efb682016-09-14 17:35:43 -0700553
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700554 // Find top and bottom extent of 9-patch content on center column.
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700555 VerticalImageLine mid_col(rows, width / 2, 1, height - 2);
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000556 FindOutlineInsets(&mid_col, &nine_patch->outline.top, &nine_patch->outline.bottom);
Adam Lesinski21efb682016-09-14 17:35:43 -0700557
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000558 const int32_t outline_width = (width - 2) - nine_patch->outline.left - nine_patch->outline.right;
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700559 const int32_t outline_height =
560 (height - 2) - nine_patch->outline.top - nine_patch->outline.bottom;
Adam Lesinski21efb682016-09-14 17:35:43 -0700561
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700562 // Find the largest alpha value within the outline area.
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000563 HorizontalImageLine outline_mid_row(rows, 1 + nine_patch->outline.left,
564 1 + nine_patch->outline.top + (outline_height / 2),
565 outline_width);
566 VerticalImageLine outline_mid_col(rows, 1 + nine_patch->outline.left + (outline_width / 2),
567 1 + nine_patch->outline.top, outline_height);
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700568 nine_patch->outline_alpha =
569 std::max(FindMaxAlpha(&outline_mid_row), FindMaxAlpha(&outline_mid_col));
Adam Lesinski21efb682016-09-14 17:35:43 -0700570
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700571 // Assuming the image is a round rect, compute the radius by marching
572 // diagonally from the top left corner towards the center.
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000573 DiagonalImageLine diagonal(rows, 1 + nine_patch->outline.left, 1 + nine_patch->outline.top, 1, 1,
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700574 std::min(outline_width, outline_height));
575 int32_t top_left, bottom_right;
576 FindOutlineInsets(&diagonal, &top_left, &bottom_right);
Adam Lesinski21efb682016-09-14 17:35:43 -0700577
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700578 /* Determine source radius based upon inset:
579 * sqrt(r^2 + r^2) = sqrt(i^2 + i^2) + r
580 * sqrt(2) * r = sqrt(2) * i + r
581 * (sqrt(2) - 1) * r = sqrt(2) * i
582 * r = sqrt(2) / (sqrt(2) - 1) * i
583 */
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700584 nine_patch->outline_radius = 3.4142f * top_left;
585 return nine_patch;
Adam Lesinski21efb682016-09-14 17:35:43 -0700586}
587
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700588std::unique_ptr<uint8_t[]> NinePatch::SerializeBase(size_t* outLen) const {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700589 android::Res_png_9patch data;
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700590 data.numXDivs = static_cast<uint8_t>(horizontal_stretch_regions.size()) * 2;
591 data.numYDivs = static_cast<uint8_t>(vertical_stretch_regions.size()) * 2;
592 data.numColors = static_cast<uint8_t>(region_colors.size());
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700593 data.paddingLeft = padding.left;
594 data.paddingRight = padding.right;
595 data.paddingTop = padding.top;
596 data.paddingBottom = padding.bottom;
Adam Lesinski21efb682016-09-14 17:35:43 -0700597
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700598 auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[data.serializedSize()]);
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000599 android::Res_png_9patch::serialize(data, (const int32_t*)horizontal_stretch_regions.data(),
600 (const int32_t*)vertical_stretch_regions.data(),
601 region_colors.data(), buffer.get());
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700602 // Convert to file endianness.
603 reinterpret_cast<android::Res_png_9patch*>(buffer.get())->deviceToFile();
Adam Lesinskiedba9412016-10-04 17:33:04 -0700604
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700605 *outLen = data.serializedSize();
606 return buffer;
Adam Lesinski21efb682016-09-14 17:35:43 -0700607}
608
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000609std::unique_ptr<uint8_t[]> NinePatch::SerializeLayoutBounds(size_t* out_len) const {
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700610 size_t chunk_len = sizeof(uint32_t) * 4;
611 auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[chunk_len]);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700612 uint8_t* cursor = buffer.get();
Adam Lesinski21efb682016-09-14 17:35:43 -0700613
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700614 memcpy(cursor, &layout_bounds.left, sizeof(layout_bounds.left));
615 cursor += sizeof(layout_bounds.left);
Adam Lesinski21efb682016-09-14 17:35:43 -0700616
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700617 memcpy(cursor, &layout_bounds.top, sizeof(layout_bounds.top));
618 cursor += sizeof(layout_bounds.top);
Adam Lesinski21efb682016-09-14 17:35:43 -0700619
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700620 memcpy(cursor, &layout_bounds.right, sizeof(layout_bounds.right));
621 cursor += sizeof(layout_bounds.right);
Adam Lesinski21efb682016-09-14 17:35:43 -0700622
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700623 memcpy(cursor, &layout_bounds.bottom, sizeof(layout_bounds.bottom));
624 cursor += sizeof(layout_bounds.bottom);
Adam Lesinski21efb682016-09-14 17:35:43 -0700625
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700626 *out_len = chunk_len;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700627 return buffer;
Adam Lesinski21efb682016-09-14 17:35:43 -0700628}
629
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000630std::unique_ptr<uint8_t[]> NinePatch::SerializeRoundedRectOutline(size_t* out_len) const {
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700631 size_t chunk_len = sizeof(uint32_t) * 6;
632 auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[chunk_len]);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700633 uint8_t* cursor = buffer.get();
Adam Lesinski21efb682016-09-14 17:35:43 -0700634
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700635 memcpy(cursor, &outline.left, sizeof(outline.left));
636 cursor += sizeof(outline.left);
Adam Lesinski21efb682016-09-14 17:35:43 -0700637
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700638 memcpy(cursor, &outline.top, sizeof(outline.top));
639 cursor += sizeof(outline.top);
Adam Lesinski21efb682016-09-14 17:35:43 -0700640
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700641 memcpy(cursor, &outline.right, sizeof(outline.right));
642 cursor += sizeof(outline.right);
Adam Lesinski21efb682016-09-14 17:35:43 -0700643
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700644 memcpy(cursor, &outline.bottom, sizeof(outline.bottom));
645 cursor += sizeof(outline.bottom);
Adam Lesinski21efb682016-09-14 17:35:43 -0700646
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700647 *((float*)cursor) = outline_radius;
648 cursor += sizeof(outline_radius);
Adam Lesinski21efb682016-09-14 17:35:43 -0700649
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700650 *((uint32_t*)cursor) = outline_alpha;
Adam Lesinski21efb682016-09-14 17:35:43 -0700651
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700652 *out_len = chunk_len;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700653 return buffer;
Adam Lesinski21efb682016-09-14 17:35:43 -0700654}
655
656::std::ostream& operator<<(::std::ostream& out, const Range& range) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700657 return out << "[" << range.start << ", " << range.end << ")";
Adam Lesinski21efb682016-09-14 17:35:43 -0700658}
659
660::std::ostream& operator<<(::std::ostream& out, const Bounds& bounds) {
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000661 return out << "l=" << bounds.left << " t=" << bounds.top << " r=" << bounds.right
662 << " b=" << bounds.bottom;
663}
664
665template <typename T>
666std::ostream& operator<<(std::ostream& os, const std::vector<T>& v) {
667 for (int i = 0; i < v.size(); ++i) {
668 os << v[i];
669 if (i != v.size() - 1) os << " ";
670 }
671 return os;
Adam Lesinski21efb682016-09-14 17:35:43 -0700672}
673
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700674::std::ostream& operator<<(::std::ostream& out, const NinePatch& nine_patch) {
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000675 return out << "horizontalStretch:" << nine_patch.horizontal_stretch_regions
676 << " verticalStretch:" << nine_patch.vertical_stretch_regions
677 << " padding: " << nine_patch.padding << ", bounds: " << nine_patch.layout_bounds
678 << ", outline: " << nine_patch.outline << " rad=" << nine_patch.outline_radius
Adam Lesinskice5e56e22016-10-21 17:56:45 -0700679 << " alpha=" << nine_patch.outline_alpha;
Adam Lesinski21efb682016-09-14 17:35:43 -0700680}
681
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000682} // namespace android