blob: c931da48c889235c35fd5416da04381f43e394f0 [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
17#include "compile/Image.h"
Adam Lesinski21efb682016-09-14 17:35:43 -070018
Adam Lesinski21efb682016-09-14 17:35:43 -070019#include <sstream>
20#include <string>
21#include <vector>
22
Adam Lesinskice5e56e2016-10-21 17:56:45 -070023#include "androidfw/ResourceTypes.h"
Adam Lesinskid5083f62017-01-16 15:07:21 -080024#include "androidfw/StringPiece.h"
Adam Lesinskice5e56e2016-10-21 17:56:45 -070025
Adam Lesinskice5e56e2016-10-21 17:56:45 -070026#include "util/Util.h"
27
Adam Lesinskid5083f62017-01-16 15:07:21 -080028using android::StringPiece;
29
Adam Lesinski21efb682016-09-14 17:35:43 -070030namespace aapt {
31
32// Colors in the format 0xAARRGGBB (the way 9-patch expects it).
33constexpr static const uint32_t kColorOpaqueWhite = 0xffffffffu;
34constexpr static const uint32_t kColorOpaqueBlack = 0xff000000u;
Adam Lesinskicacb28f2016-10-19 12:18:14 -070035constexpr static const uint32_t kColorOpaqueRed = 0xffff0000u;
Adam Lesinski21efb682016-09-14 17:35:43 -070036
37constexpr static const uint32_t kPrimaryColor = kColorOpaqueBlack;
38constexpr static const uint32_t kSecondaryColor = kColorOpaqueRed;
39
40/**
41 * Returns the alpha value encoded in the 0xAARRGBB encoded pixel.
42 */
Adam Lesinskice5e56e2016-10-21 17:56:45 -070043static uint32_t get_alpha(uint32_t color);
Adam Lesinski21efb682016-09-14 17:35:43 -070044
45/**
46 * Determines whether a color on an ImageLine is valid.
47 * A 9patch image may use a transparent color as neutral,
48 * or a fully opaque white color as neutral, based on the
49 * pixel color at (0,0) of the image. One or the other is fine,
50 * but we need to ensure consistency throughout the image.
51 */
52class ColorValidator {
Adam Lesinskicacb28f2016-10-19 12:18:14 -070053 public:
54 virtual ~ColorValidator() = default;
Adam Lesinski21efb682016-09-14 17:35:43 -070055
Adam Lesinskicacb28f2016-10-19 12:18:14 -070056 /**
57 * Returns true if the color specified is a neutral color
58 * (no padding, stretching, or optical bounds).
59 */
Adam Lesinskice5e56e2016-10-21 17:56:45 -070060 virtual bool IsNeutralColor(uint32_t color) const = 0;
Adam Lesinski21efb682016-09-14 17:35:43 -070061
Adam Lesinskicacb28f2016-10-19 12:18:14 -070062 /**
63 * Returns true if the color is either a neutral color
64 * or one denoting padding, stretching, or optical bounds.
65 */
Adam Lesinskice5e56e2016-10-21 17:56:45 -070066 bool IsValidColor(uint32_t color) const {
Adam Lesinskicacb28f2016-10-19 12:18:14 -070067 switch (color) {
68 case kPrimaryColor:
69 case kSecondaryColor:
70 return true;
Adam Lesinski21efb682016-09-14 17:35:43 -070071 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -070072 return IsNeutralColor(color);
Adam Lesinskicacb28f2016-10-19 12:18:14 -070073 }
Adam Lesinski21efb682016-09-14 17:35:43 -070074};
75
76// Walks an ImageLine and records Ranges of primary and secondary colors.
Adam Lesinskicacb28f2016-10-19 12:18:14 -070077// The primary color is black and is used to denote a padding or stretching
78// range,
Adam Lesinski21efb682016-09-14 17:35:43 -070079// depending on which border we're iterating over.
80// The secondary color is red and is used to denote optical bounds.
81//
Adam Lesinskicacb28f2016-10-19 12:18:14 -070082// An ImageLine is a templated-interface that would look something like this if
83// it
Adam Lesinski21efb682016-09-14 17:35:43 -070084// were polymorphic:
85//
86// class ImageLine {
87// public:
Adam Lesinskice5e56e2016-10-21 17:56:45 -070088// virtual int32_t GetLength() const = 0;
89// virtual uint32_t GetColor(int32_t idx) const = 0;
Adam Lesinski21efb682016-09-14 17:35:43 -070090// };
91//
92template <typename ImageLine>
Adam Lesinskice5e56e2016-10-21 17:56:45 -070093static bool FillRanges(const ImageLine* image_line,
94 const ColorValidator* color_validator,
95 std::vector<Range>* primary_ranges,
96 std::vector<Range>* secondary_ranges,
97 std::string* out_err) {
98 const int32_t length = image_line->GetLength();
Adam Lesinski21efb682016-09-14 17:35:43 -070099
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700100 uint32_t last_color = 0xffffffffu;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700101 for (int32_t idx = 1; idx < length - 1; idx++) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700102 const uint32_t color = image_line->GetColor(idx);
103 if (!color_validator->IsValidColor(color)) {
104 *out_err = "found an invalid color";
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700105 return false;
Adam Lesinski21efb682016-09-14 17:35:43 -0700106 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700107
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700108 if (color != last_color) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700109 // We are ending a range. Which range?
110 // note: encode the x offset without the final 1 pixel border.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700111 if (last_color == kPrimaryColor) {
112 primary_ranges->back().end = idx - 1;
113 } else if (last_color == kSecondaryColor) {
114 secondary_ranges->back().end = idx - 1;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700115 }
116
117 // We are starting a range. Which range?
118 // note: encode the x offset without the final 1 pixel border.
119 if (color == kPrimaryColor) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700120 primary_ranges->push_back(Range(idx - 1, length - 2));
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700121 } else if (color == kSecondaryColor) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700122 secondary_ranges->push_back(Range(idx - 1, length - 2));
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700123 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700124 last_color = color;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700125 }
126 }
127 return true;
Adam Lesinski21efb682016-09-14 17:35:43 -0700128}
129
130/**
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700131 * Iterates over a row in an image. Implements the templated ImageLine
132 * interface.
Adam Lesinski21efb682016-09-14 17:35:43 -0700133 */
134class HorizontalImageLine {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700135 public:
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700136 explicit HorizontalImageLine(uint8_t** rows, int32_t xoffset, int32_t yoffset,
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700137 int32_t length)
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700138 : rows_(rows), xoffset_(xoffset), yoffset_(yoffset), length_(length) {}
Adam Lesinski21efb682016-09-14 17:35:43 -0700139
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700140 inline int32_t GetLength() const { return length_; }
Adam Lesinski21efb682016-09-14 17:35:43 -0700141
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700142 inline uint32_t GetColor(int32_t idx) const {
143 return NinePatch::PackRGBA(rows_[yoffset_] + (idx + xoffset_) * 4);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700144 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700145
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700146 private:
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700147 uint8_t** rows_;
148 int32_t xoffset_, yoffset_, length_;
Adam Lesinski21efb682016-09-14 17:35:43 -0700149
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700150 DISALLOW_COPY_AND_ASSIGN(HorizontalImageLine);
Adam Lesinski21efb682016-09-14 17:35:43 -0700151};
152
153/**
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700154 * Iterates over a column in an image. Implements the templated ImageLine
155 * interface.
Adam Lesinski21efb682016-09-14 17:35:43 -0700156 */
157class VerticalImageLine {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700158 public:
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700159 explicit VerticalImageLine(uint8_t** rows, int32_t xoffset, int32_t yoffset,
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700160 int32_t length)
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700161 : rows_(rows), xoffset_(xoffset), yoffset_(yoffset), length_(length) {}
Adam Lesinski21efb682016-09-14 17:35:43 -0700162
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700163 inline int32_t GetLength() const { return length_; }
Adam Lesinski21efb682016-09-14 17:35:43 -0700164
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700165 inline uint32_t GetColor(int32_t idx) const {
166 return NinePatch::PackRGBA(rows_[yoffset_ + idx] + (xoffset_ * 4));
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700167 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700168
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700169 private:
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700170 uint8_t** rows_;
171 int32_t xoffset_, yoffset_, length_;
Adam Lesinski21efb682016-09-14 17:35:43 -0700172
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700173 DISALLOW_COPY_AND_ASSIGN(VerticalImageLine);
Adam Lesinski21efb682016-09-14 17:35:43 -0700174};
175
176class DiagonalImageLine {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700177 public:
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700178 explicit DiagonalImageLine(uint8_t** rows, int32_t xoffset, int32_t yoffset,
179 int32_t xstep, int32_t ystep, int32_t length)
180 : rows_(rows),
181 xoffset_(xoffset),
182 yoffset_(yoffset),
183 xstep_(xstep),
184 ystep_(ystep),
185 length_(length) {}
Adam Lesinski21efb682016-09-14 17:35:43 -0700186
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700187 inline int32_t GetLength() const { return length_; }
Adam Lesinski21efb682016-09-14 17:35:43 -0700188
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700189 inline uint32_t GetColor(int32_t idx) const {
190 return NinePatch::PackRGBA(rows_[yoffset_ + (idx * ystep_)] +
191 ((idx + xoffset_) * xstep_) * 4);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700192 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700193
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700194 private:
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700195 uint8_t** rows_;
196 int32_t xoffset_, yoffset_, xstep_, ystep_, length_;
Adam Lesinski21efb682016-09-14 17:35:43 -0700197
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700198 DISALLOW_COPY_AND_ASSIGN(DiagonalImageLine);
Adam Lesinski21efb682016-09-14 17:35:43 -0700199};
200
201class TransparentNeutralColorValidator : public ColorValidator {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700202 public:
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700203 bool IsNeutralColor(uint32_t color) const override {
204 return get_alpha(color) == 0;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700205 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700206};
207
208class WhiteNeutralColorValidator : public ColorValidator {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700209 public:
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700210 bool IsNeutralColor(uint32_t color) const override {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700211 return color == kColorOpaqueWhite;
212 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700213};
214
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700215inline static uint32_t get_alpha(uint32_t color) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700216 return (color & 0xff000000u) >> 24;
Adam Lesinski21efb682016-09-14 17:35:43 -0700217}
218
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700219static bool PopulateBounds(const std::vector<Range>& padding,
220 const std::vector<Range>& layout_bounds,
221 const std::vector<Range>& stretch_regions,
222 const int32_t length, int32_t* padding_start,
223 int32_t* padding_end, int32_t* layout_start,
224 int32_t* layout_end, const StringPiece& edge_name,
225 std::string* out_err) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700226 if (padding.size() > 1) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700227 std::stringstream err_stream;
228 err_stream << "too many padding sections on " << edge_name << " border";
229 *out_err = err_stream.str();
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700230 return false;
231 }
232
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700233 *padding_start = 0;
234 *padding_end = 0;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700235 if (!padding.empty()) {
236 const Range& range = padding.front();
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700237 *padding_start = range.start;
238 *padding_end = length - range.end;
239 } else if (!stretch_regions.empty()) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700240 // No padding was defined. Compute the padding from the first and last
241 // stretch regions.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700242 *padding_start = stretch_regions.front().start;
243 *padding_end = length - stretch_regions.back().end;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700244 }
245
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700246 if (layout_bounds.size() > 2) {
247 std::stringstream err_stream;
248 err_stream << "too many layout bounds sections on " << edge_name
249 << " border";
250 *out_err = err_stream.str();
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700251 return false;
252 }
253
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700254 *layout_start = 0;
255 *layout_end = 0;
256 if (layout_bounds.size() >= 1) {
257 const Range& range = layout_bounds.front();
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700258 // If there is only one layout bound segment, it might not start at 0, but
259 // then it should
260 // end at length.
261 if (range.start != 0 && range.end != length) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700262 std::stringstream err_stream;
263 err_stream << "layout bounds on " << edge_name
264 << " border must start at edge";
265 *out_err = err_stream.str();
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700266 return false;
267 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700268 *layout_start = range.end;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700269
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700270 if (layout_bounds.size() >= 2) {
271 const Range& range = layout_bounds.back();
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700272 if (range.end != length) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700273 std::stringstream err_stream;
274 err_stream << "layout bounds on " << edge_name
275 << " border must start at edge";
276 *out_err = err_stream.str();
Adam Lesinski21efb682016-09-14 17:35:43 -0700277 return false;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700278 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700279 *layout_end = length - range.start;
Adam Lesinski21efb682016-09-14 17:35:43 -0700280 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700281 }
282 return true;
Adam Lesinski21efb682016-09-14 17:35:43 -0700283}
284
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700285static int32_t CalculateSegmentCount(const std::vector<Range>& stretch_regions,
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700286 int32_t length) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700287 if (stretch_regions.size() == 0) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700288 return 0;
289 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700290
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700291 const bool start_is_fixed = stretch_regions.front().start != 0;
292 const bool end_is_fixed = stretch_regions.back().end != length;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700293 int32_t modifier = 0;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700294 if (start_is_fixed && end_is_fixed) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700295 modifier = 1;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700296 } else if (!start_is_fixed && !end_is_fixed) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700297 modifier = -1;
298 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700299 return static_cast<int32_t>(stretch_regions.size()) * 2 + modifier;
Adam Lesinski21efb682016-09-14 17:35:43 -0700300}
301
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700302static uint32_t GetRegionColor(uint8_t** rows, const Bounds& region) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700303 // Sample the first pixel to compare against.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700304 const uint32_t expected_color =
305 NinePatch::PackRGBA(rows[region.top] + region.left * 4);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700306 for (int32_t y = region.top; y < region.bottom; y++) {
307 const uint8_t* row = rows[y];
308 for (int32_t x = region.left; x < region.right; x++) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700309 const uint32_t color = NinePatch::PackRGBA(row + x * 4);
310 if (get_alpha(color) == 0) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700311 // The color is transparent.
312 // If the expectedColor is not transparent, NO_COLOR.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700313 if (get_alpha(expected_color) != 0) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700314 return android::Res_png_9patch::NO_COLOR;
Adam Lesinski21efb682016-09-14 17:35:43 -0700315 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700316 } else if (color != expected_color) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700317 return android::Res_png_9patch::NO_COLOR;
318 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700319 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700320 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700321
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700322 if (get_alpha(expected_color) == 0) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700323 return android::Res_png_9patch::TRANSPARENT_COLOR;
324 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700325 return expected_color;
Adam Lesinski21efb682016-09-14 17:35:43 -0700326}
327
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700328// Fills out_colors with each 9-patch section's color. If the whole section is
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700329// transparent,
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700330// it gets the special TRANSPARENT color. If the whole section is the same
331// color, it is assigned
332// that color. Otherwise it gets the special NO_COLOR color.
Adam Lesinski21efb682016-09-14 17:35:43 -0700333//
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700334// Note that the rows contain the 9-patch 1px border, and the indices in the
335// stretch regions are
336// already offset to exclude the border. This means that each time the rows are
337// accessed,
Adam Lesinski21efb682016-09-14 17:35:43 -0700338// the indices must be offset by 1.
339//
340// width and height also include the 9-patch 1px border.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700341static void CalculateRegionColors(
342 uint8_t** rows, const std::vector<Range>& horizontal_stretch_regions,
343 const std::vector<Range>& vertical_stretch_regions, const int32_t width,
344 const int32_t height, std::vector<uint32_t>* out_colors) {
345 int32_t next_top = 0;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700346 Bounds bounds;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700347 auto row_iter = vertical_stretch_regions.begin();
348 while (next_top != height) {
349 if (row_iter != vertical_stretch_regions.end()) {
350 if (next_top != row_iter->start) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700351 // This is a fixed segment.
352 // Offset the bounds by 1 to accommodate the border.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700353 bounds.top = next_top + 1;
354 bounds.bottom = row_iter->start + 1;
355 next_top = row_iter->start;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700356 } else {
357 // This is a stretchy segment.
358 // Offset the bounds by 1 to accommodate the border.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700359 bounds.top = row_iter->start + 1;
360 bounds.bottom = row_iter->end + 1;
361 next_top = row_iter->end;
362 ++row_iter;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700363 }
364 } else {
365 // This is the end, fixed section.
366 // Offset the bounds by 1 to accommodate the border.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700367 bounds.top = next_top + 1;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700368 bounds.bottom = height + 1;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700369 next_top = height;
Adam Lesinski21efb682016-09-14 17:35:43 -0700370 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700371
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700372 int32_t next_left = 0;
373 auto col_iter = horizontal_stretch_regions.begin();
374 while (next_left != width) {
375 if (col_iter != horizontal_stretch_regions.end()) {
376 if (next_left != col_iter->start) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700377 // This is a fixed segment.
378 // Offset the bounds by 1 to accommodate the border.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700379 bounds.left = next_left + 1;
380 bounds.right = col_iter->start + 1;
381 next_left = col_iter->start;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700382 } else {
383 // This is a stretchy segment.
384 // Offset the bounds by 1 to accommodate the border.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700385 bounds.left = col_iter->start + 1;
386 bounds.right = col_iter->end + 1;
387 next_left = col_iter->end;
388 ++col_iter;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700389 }
390 } else {
391 // This is the end, fixed section.
392 // Offset the bounds by 1 to accommodate the border.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700393 bounds.left = next_left + 1;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700394 bounds.right = width + 1;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700395 next_left = width;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700396 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700397 out_colors->push_back(GetRegionColor(rows, bounds));
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700398 }
399 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700400}
401
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700402// Calculates the insets of a row/column of pixels based on where the largest
403// alpha value begins
Adam Lesinski21efb682016-09-14 17:35:43 -0700404// (on both sides).
405template <typename ImageLine>
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700406static void FindOutlineInsets(const ImageLine* image_line, int32_t* out_start,
407 int32_t* out_end) {
408 *out_start = 0;
409 *out_end = 0;
Adam Lesinski21efb682016-09-14 17:35:43 -0700410
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700411 const int32_t length = image_line->GetLength();
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700412 if (length < 3) {
Adam Lesinski21efb682016-09-14 17:35:43 -0700413 return;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700414 }
415
416 // If the length is odd, we want both sides to process the center pixel,
417 // so we use two different midpoints (to account for < and <= in the different
418 // loops).
419 const int32_t mid2 = length / 2;
420 const int32_t mid1 = mid2 + (length % 2);
421
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700422 uint32_t max_alpha = 0;
423 for (int32_t i = 0; i < mid1 && max_alpha != 0xff; i++) {
424 uint32_t alpha = get_alpha(image_line->GetColor(i));
425 if (alpha > max_alpha) {
426 max_alpha = alpha;
427 *out_start = i;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700428 }
429 }
430
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700431 max_alpha = 0;
432 for (int32_t i = length - 1; i >= mid2 && max_alpha != 0xff; i--) {
433 uint32_t alpha = get_alpha(image_line->GetColor(i));
434 if (alpha > max_alpha) {
435 max_alpha = alpha;
436 *out_end = length - (i + 1);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700437 }
438 }
439 return;
Adam Lesinski21efb682016-09-14 17:35:43 -0700440}
441
442template <typename ImageLine>
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700443static uint32_t FindMaxAlpha(const ImageLine* image_line) {
444 const int32_t length = image_line->GetLength();
445 uint32_t max_alpha = 0;
446 for (int32_t idx = 0; idx < length && max_alpha != 0xff; idx++) {
447 uint32_t alpha = get_alpha(image_line->GetColor(idx));
448 if (alpha > max_alpha) {
449 max_alpha = alpha;
Adam Lesinski21efb682016-09-14 17:35:43 -0700450 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700451 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700452 return max_alpha;
Adam Lesinski21efb682016-09-14 17:35:43 -0700453}
454
455// Pack the pixels in as 0xAARRGGBB (as 9-patch expects it).
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700456uint32_t NinePatch::PackRGBA(const uint8_t* pixel) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700457 return (pixel[3] << 24) | (pixel[0] << 16) | (pixel[1] << 8) | pixel[2];
Adam Lesinski21efb682016-09-14 17:35:43 -0700458}
459
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700460std::unique_ptr<NinePatch> NinePatch::Create(uint8_t** rows,
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700461 const int32_t width,
462 const int32_t height,
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700463 std::string* out_err) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700464 if (width < 3 || height < 3) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700465 *out_err = "image must be at least 3x3 (1x1 image with 1 pixel border)";
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700466 return {};
467 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700468
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700469 std::vector<Range> horizontal_padding;
470 std::vector<Range> horizontal_layout_bounds;
471 std::vector<Range> vertical_padding;
472 std::vector<Range> vertical_layout_bounds;
473 std::vector<Range> unexpected_ranges;
474 std::unique_ptr<ColorValidator> color_validator;
Adam Lesinski21efb682016-09-14 17:35:43 -0700475
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700476 if (rows[0][3] == 0) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700477 color_validator = util::make_unique<TransparentNeutralColorValidator>();
478 } else if (PackRGBA(rows[0]) == kColorOpaqueWhite) {
479 color_validator = util::make_unique<WhiteNeutralColorValidator>();
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700480 } else {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700481 *out_err =
482 "top-left corner pixel must be either opaque white or transparent";
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700483 return {};
484 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700485
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700486 // Private constructor, can't use make_unique.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700487 auto nine_patch = std::unique_ptr<NinePatch>(new NinePatch());
Adam Lesinski21efb682016-09-14 17:35:43 -0700488
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700489 HorizontalImageLine top_row(rows, 0, 0, width);
490 if (!FillRanges(&top_row, color_validator.get(),
491 &nine_patch->horizontal_stretch_regions, &unexpected_ranges,
492 out_err)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700493 return {};
494 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700495
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700496 if (!unexpected_ranges.empty()) {
497 const Range& range = unexpected_ranges[0];
498 std::stringstream err_stream;
499 err_stream << "found unexpected optical bounds (red pixel) on top border "
500 << "at x=" << range.start + 1;
501 *out_err = err_stream.str();
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700502 return {};
503 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700504
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700505 VerticalImageLine left_col(rows, 0, 0, height);
506 if (!FillRanges(&left_col, color_validator.get(),
507 &nine_patch->vertical_stretch_regions, &unexpected_ranges,
508 out_err)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700509 return {};
510 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700511
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700512 if (!unexpected_ranges.empty()) {
513 const Range& range = unexpected_ranges[0];
514 std::stringstream err_stream;
515 err_stream << "found unexpected optical bounds (red pixel) on left border "
516 << "at y=" << range.start + 1;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700517 return {};
518 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700519
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700520 HorizontalImageLine bottom_row(rows, 0, height - 1, width);
521 if (!FillRanges(&bottom_row, color_validator.get(), &horizontal_padding,
522 &horizontal_layout_bounds, out_err)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700523 return {};
524 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700525
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700526 if (!PopulateBounds(horizontal_padding, horizontal_layout_bounds,
527 nine_patch->horizontal_stretch_regions, width - 2,
528 &nine_patch->padding.left, &nine_patch->padding.right,
529 &nine_patch->layout_bounds.left,
530 &nine_patch->layout_bounds.right, "bottom", out_err)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700531 return {};
532 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700533
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700534 VerticalImageLine right_col(rows, width - 1, 0, height);
535 if (!FillRanges(&right_col, color_validator.get(), &vertical_padding,
536 &vertical_layout_bounds, out_err)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700537 return {};
538 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700539
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700540 if (!PopulateBounds(vertical_padding, vertical_layout_bounds,
541 nine_patch->vertical_stretch_regions, height - 2,
542 &nine_patch->padding.top, &nine_patch->padding.bottom,
543 &nine_patch->layout_bounds.top,
544 &nine_patch->layout_bounds.bottom, "right", out_err)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700545 return {};
546 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700547
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700548 // Fill the region colors of the 9-patch.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700549 const int32_t num_rows =
550 CalculateSegmentCount(nine_patch->horizontal_stretch_regions, width - 2);
551 const int32_t num_cols =
552 CalculateSegmentCount(nine_patch->vertical_stretch_regions, height - 2);
553 if ((int64_t)num_rows * (int64_t)num_cols > 0x7f) {
554 *out_err = "too many regions in 9-patch";
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700555 return {};
556 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700557
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700558 nine_patch->region_colors.reserve(num_rows * num_cols);
559 CalculateRegionColors(rows, nine_patch->horizontal_stretch_regions,
560 nine_patch->vertical_stretch_regions, width - 2,
561 height - 2, &nine_patch->region_colors);
Adam Lesinski21efb682016-09-14 17:35:43 -0700562
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700563 // Compute the outline based on opacity.
Adam Lesinski21efb682016-09-14 17:35:43 -0700564
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700565 // Find left and right extent of 9-patch content on center row.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700566 HorizontalImageLine mid_row(rows, 1, height / 2, width - 2);
567 FindOutlineInsets(&mid_row, &nine_patch->outline.left,
568 &nine_patch->outline.right);
Adam Lesinski21efb682016-09-14 17:35:43 -0700569
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700570 // Find top and bottom extent of 9-patch content on center column.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700571 VerticalImageLine mid_col(rows, width / 2, 1, height - 2);
572 FindOutlineInsets(&mid_col, &nine_patch->outline.top,
573 &nine_patch->outline.bottom);
Adam Lesinski21efb682016-09-14 17:35:43 -0700574
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700575 const int32_t outline_width =
576 (width - 2) - nine_patch->outline.left - nine_patch->outline.right;
577 const int32_t outline_height =
578 (height - 2) - nine_patch->outline.top - nine_patch->outline.bottom;
Adam Lesinski21efb682016-09-14 17:35:43 -0700579
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700580 // Find the largest alpha value within the outline area.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700581 HorizontalImageLine outline_mid_row(
582 rows, 1 + nine_patch->outline.left,
583 1 + nine_patch->outline.top + (outline_height / 2), outline_width);
584 VerticalImageLine outline_mid_col(
585 rows, 1 + nine_patch->outline.left + (outline_width / 2),
586 1 + nine_patch->outline.top, outline_height);
587 nine_patch->outline_alpha =
588 std::max(FindMaxAlpha(&outline_mid_row), FindMaxAlpha(&outline_mid_col));
Adam Lesinski21efb682016-09-14 17:35:43 -0700589
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700590 // Assuming the image is a round rect, compute the radius by marching
591 // diagonally from the top left corner towards the center.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700592 DiagonalImageLine diagonal(rows, 1 + nine_patch->outline.left,
593 1 + nine_patch->outline.top, 1, 1,
594 std::min(outline_width, outline_height));
595 int32_t top_left, bottom_right;
596 FindOutlineInsets(&diagonal, &top_left, &bottom_right);
Adam Lesinski21efb682016-09-14 17:35:43 -0700597
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700598 /* Determine source radius based upon inset:
599 * sqrt(r^2 + r^2) = sqrt(i^2 + i^2) + r
600 * sqrt(2) * r = sqrt(2) * i + r
601 * (sqrt(2) - 1) * r = sqrt(2) * i
602 * r = sqrt(2) / (sqrt(2) - 1) * i
603 */
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700604 nine_patch->outline_radius = 3.4142f * top_left;
605 return nine_patch;
Adam Lesinski21efb682016-09-14 17:35:43 -0700606}
607
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700608std::unique_ptr<uint8_t[]> NinePatch::SerializeBase(size_t* outLen) const {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700609 android::Res_png_9patch data;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700610 data.numXDivs = static_cast<uint8_t>(horizontal_stretch_regions.size()) * 2;
611 data.numYDivs = static_cast<uint8_t>(vertical_stretch_regions.size()) * 2;
612 data.numColors = static_cast<uint8_t>(region_colors.size());
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700613 data.paddingLeft = padding.left;
614 data.paddingRight = padding.right;
615 data.paddingTop = padding.top;
616 data.paddingBottom = padding.bottom;
Adam Lesinski21efb682016-09-14 17:35:43 -0700617
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700618 auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[data.serializedSize()]);
619 android::Res_png_9patch::serialize(
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700620 data, (const int32_t*)horizontal_stretch_regions.data(),
621 (const int32_t*)vertical_stretch_regions.data(), region_colors.data(),
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700622 buffer.get());
623 // Convert to file endianness.
624 reinterpret_cast<android::Res_png_9patch*>(buffer.get())->deviceToFile();
Adam Lesinskiedba9412016-10-04 17:33:04 -0700625
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700626 *outLen = data.serializedSize();
627 return buffer;
Adam Lesinski21efb682016-09-14 17:35:43 -0700628}
629
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700630std::unique_ptr<uint8_t[]> NinePatch::SerializeLayoutBounds(
631 size_t* out_len) const {
632 size_t chunk_len = sizeof(uint32_t) * 4;
633 auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[chunk_len]);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700634 uint8_t* cursor = buffer.get();
Adam Lesinski21efb682016-09-14 17:35:43 -0700635
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700636 memcpy(cursor, &layout_bounds.left, sizeof(layout_bounds.left));
637 cursor += sizeof(layout_bounds.left);
Adam Lesinski21efb682016-09-14 17:35:43 -0700638
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700639 memcpy(cursor, &layout_bounds.top, sizeof(layout_bounds.top));
640 cursor += sizeof(layout_bounds.top);
Adam Lesinski21efb682016-09-14 17:35:43 -0700641
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700642 memcpy(cursor, &layout_bounds.right, sizeof(layout_bounds.right));
643 cursor += sizeof(layout_bounds.right);
Adam Lesinski21efb682016-09-14 17:35:43 -0700644
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700645 memcpy(cursor, &layout_bounds.bottom, sizeof(layout_bounds.bottom));
646 cursor += sizeof(layout_bounds.bottom);
Adam Lesinski21efb682016-09-14 17:35:43 -0700647
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700648 *out_len = chunk_len;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700649 return buffer;
Adam Lesinski21efb682016-09-14 17:35:43 -0700650}
651
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700652std::unique_ptr<uint8_t[]> NinePatch::SerializeRoundedRectOutline(
653 size_t* out_len) const {
654 size_t chunk_len = sizeof(uint32_t) * 6;
655 auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[chunk_len]);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700656 uint8_t* cursor = buffer.get();
Adam Lesinski21efb682016-09-14 17:35:43 -0700657
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700658 memcpy(cursor, &outline.left, sizeof(outline.left));
659 cursor += sizeof(outline.left);
Adam Lesinski21efb682016-09-14 17:35:43 -0700660
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700661 memcpy(cursor, &outline.top, sizeof(outline.top));
662 cursor += sizeof(outline.top);
Adam Lesinski21efb682016-09-14 17:35:43 -0700663
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700664 memcpy(cursor, &outline.right, sizeof(outline.right));
665 cursor += sizeof(outline.right);
Adam Lesinski21efb682016-09-14 17:35:43 -0700666
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700667 memcpy(cursor, &outline.bottom, sizeof(outline.bottom));
668 cursor += sizeof(outline.bottom);
Adam Lesinski21efb682016-09-14 17:35:43 -0700669
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700670 *((float*)cursor) = outline_radius;
671 cursor += sizeof(outline_radius);
Adam Lesinski21efb682016-09-14 17:35:43 -0700672
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700673 *((uint32_t*)cursor) = outline_alpha;
Adam Lesinski21efb682016-09-14 17:35:43 -0700674
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700675 *out_len = chunk_len;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700676 return buffer;
Adam Lesinski21efb682016-09-14 17:35:43 -0700677}
678
679::std::ostream& operator<<(::std::ostream& out, const Range& range) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700680 return out << "[" << range.start << ", " << range.end << ")";
Adam Lesinski21efb682016-09-14 17:35:43 -0700681}
682
683::std::ostream& operator<<(::std::ostream& out, const Bounds& bounds) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700684 return out << "l=" << bounds.left << " t=" << bounds.top
685 << " r=" << bounds.right << " b=" << bounds.bottom;
Adam Lesinski21efb682016-09-14 17:35:43 -0700686}
687
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700688::std::ostream& operator<<(::std::ostream& out, const NinePatch& nine_patch) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700689 return out << "horizontalStretch:"
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700690 << util::Joiner(nine_patch.horizontal_stretch_regions, " ")
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700691 << " verticalStretch:"
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700692 << util::Joiner(nine_patch.vertical_stretch_regions, " ")
693 << " padding: " << nine_patch.padding
694 << ", bounds: " << nine_patch.layout_bounds
695 << ", outline: " << nine_patch.outline
696 << " rad=" << nine_patch.outline_radius
697 << " alpha=" << nine_patch.outline_alpha;
Adam Lesinski21efb682016-09-14 17:35:43 -0700698}
699
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700700} // namespace aapt