blob: eab5c97c437cfd894a0ef7c18d4de39aefb179d8 [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"
24
25#include "util/StringPiece.h"
26#include "util/Util.h"
27
Adam Lesinski21efb682016-09-14 17:35:43 -070028namespace aapt {
29
30// Colors in the format 0xAARRGGBB (the way 9-patch expects it).
31constexpr static const uint32_t kColorOpaqueWhite = 0xffffffffu;
32constexpr static const uint32_t kColorOpaqueBlack = 0xff000000u;
Adam Lesinskicacb28f2016-10-19 12:18:14 -070033constexpr static const uint32_t kColorOpaqueRed = 0xffff0000u;
Adam Lesinski21efb682016-09-14 17:35:43 -070034
35constexpr static const uint32_t kPrimaryColor = kColorOpaqueBlack;
36constexpr static const uint32_t kSecondaryColor = kColorOpaqueRed;
37
38/**
39 * Returns the alpha value encoded in the 0xAARRGBB encoded pixel.
40 */
Adam Lesinskice5e56e2016-10-21 17:56:45 -070041static uint32_t get_alpha(uint32_t color);
Adam Lesinski21efb682016-09-14 17:35:43 -070042
43/**
44 * Determines whether a color on an ImageLine is valid.
45 * A 9patch image may use a transparent color as neutral,
46 * or a fully opaque white color as neutral, based on the
47 * pixel color at (0,0) of the image. One or the other is fine,
48 * but we need to ensure consistency throughout the image.
49 */
50class ColorValidator {
Adam Lesinskicacb28f2016-10-19 12:18:14 -070051 public:
52 virtual ~ColorValidator() = default;
Adam Lesinski21efb682016-09-14 17:35:43 -070053
Adam Lesinskicacb28f2016-10-19 12:18:14 -070054 /**
55 * Returns true if the color specified is a neutral color
56 * (no padding, stretching, or optical bounds).
57 */
Adam Lesinskice5e56e2016-10-21 17:56:45 -070058 virtual bool IsNeutralColor(uint32_t color) const = 0;
Adam Lesinski21efb682016-09-14 17:35:43 -070059
Adam Lesinskicacb28f2016-10-19 12:18:14 -070060 /**
61 * Returns true if the color is either a neutral color
62 * or one denoting padding, stretching, or optical bounds.
63 */
Adam Lesinskice5e56e2016-10-21 17:56:45 -070064 bool IsValidColor(uint32_t color) const {
Adam Lesinskicacb28f2016-10-19 12:18:14 -070065 switch (color) {
66 case kPrimaryColor:
67 case kSecondaryColor:
68 return true;
Adam Lesinski21efb682016-09-14 17:35:43 -070069 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -070070 return IsNeutralColor(color);
Adam Lesinskicacb28f2016-10-19 12:18:14 -070071 }
Adam Lesinski21efb682016-09-14 17:35:43 -070072};
73
74// Walks an ImageLine and records Ranges of primary and secondary colors.
Adam Lesinskicacb28f2016-10-19 12:18:14 -070075// The primary color is black and is used to denote a padding or stretching
76// range,
Adam Lesinski21efb682016-09-14 17:35:43 -070077// depending on which border we're iterating over.
78// The secondary color is red and is used to denote optical bounds.
79//
Adam Lesinskicacb28f2016-10-19 12:18:14 -070080// An ImageLine is a templated-interface that would look something like this if
81// it
Adam Lesinski21efb682016-09-14 17:35:43 -070082// were polymorphic:
83//
84// class ImageLine {
85// public:
Adam Lesinskice5e56e2016-10-21 17:56:45 -070086// virtual int32_t GetLength() const = 0;
87// virtual uint32_t GetColor(int32_t idx) const = 0;
Adam Lesinski21efb682016-09-14 17:35:43 -070088// };
89//
90template <typename ImageLine>
Adam Lesinskice5e56e2016-10-21 17:56:45 -070091static bool FillRanges(const ImageLine* image_line,
92 const ColorValidator* color_validator,
93 std::vector<Range>* primary_ranges,
94 std::vector<Range>* secondary_ranges,
95 std::string* out_err) {
96 const int32_t length = image_line->GetLength();
Adam Lesinski21efb682016-09-14 17:35:43 -070097
Adam Lesinskice5e56e2016-10-21 17:56:45 -070098 uint32_t last_color = 0xffffffffu;
Adam Lesinskicacb28f2016-10-19 12:18:14 -070099 for (int32_t idx = 1; idx < length - 1; idx++) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700100 const uint32_t color = image_line->GetColor(idx);
101 if (!color_validator->IsValidColor(color)) {
102 *out_err = "found an invalid color";
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700103 return false;
Adam Lesinski21efb682016-09-14 17:35:43 -0700104 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700105
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700106 if (color != last_color) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700107 // We are ending a range. Which range?
108 // note: encode the x offset without the final 1 pixel border.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700109 if (last_color == kPrimaryColor) {
110 primary_ranges->back().end = idx - 1;
111 } else if (last_color == kSecondaryColor) {
112 secondary_ranges->back().end = idx - 1;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700113 }
114
115 // We are starting a range. Which range?
116 // note: encode the x offset without the final 1 pixel border.
117 if (color == kPrimaryColor) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700118 primary_ranges->push_back(Range(idx - 1, length - 2));
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700119 } else if (color == kSecondaryColor) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700120 secondary_ranges->push_back(Range(idx - 1, length - 2));
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700121 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700122 last_color = color;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700123 }
124 }
125 return true;
Adam Lesinski21efb682016-09-14 17:35:43 -0700126}
127
128/**
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700129 * Iterates over a row in an image. Implements the templated ImageLine
130 * interface.
Adam Lesinski21efb682016-09-14 17:35:43 -0700131 */
132class HorizontalImageLine {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700133 public:
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700134 explicit HorizontalImageLine(uint8_t** rows, int32_t xoffset, int32_t yoffset,
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700135 int32_t length)
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700136 : rows_(rows), xoffset_(xoffset), yoffset_(yoffset), length_(length) {}
Adam Lesinski21efb682016-09-14 17:35:43 -0700137
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700138 inline int32_t GetLength() const { return length_; }
Adam Lesinski21efb682016-09-14 17:35:43 -0700139
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700140 inline uint32_t GetColor(int32_t idx) const {
141 return NinePatch::PackRGBA(rows_[yoffset_] + (idx + xoffset_) * 4);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700142 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700143
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700144 private:
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700145 uint8_t** rows_;
146 int32_t xoffset_, yoffset_, length_;
Adam Lesinski21efb682016-09-14 17:35:43 -0700147
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700148 DISALLOW_COPY_AND_ASSIGN(HorizontalImageLine);
Adam Lesinski21efb682016-09-14 17:35:43 -0700149};
150
151/**
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700152 * Iterates over a column in an image. Implements the templated ImageLine
153 * interface.
Adam Lesinski21efb682016-09-14 17:35:43 -0700154 */
155class VerticalImageLine {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700156 public:
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700157 explicit VerticalImageLine(uint8_t** rows, int32_t xoffset, int32_t yoffset,
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700158 int32_t length)
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700159 : rows_(rows), xoffset_(xoffset), yoffset_(yoffset), length_(length) {}
Adam Lesinski21efb682016-09-14 17:35:43 -0700160
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700161 inline int32_t GetLength() const { return length_; }
Adam Lesinski21efb682016-09-14 17:35:43 -0700162
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700163 inline uint32_t GetColor(int32_t idx) const {
164 return NinePatch::PackRGBA(rows_[yoffset_ + idx] + (xoffset_ * 4));
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700165 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700166
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700167 private:
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700168 uint8_t** rows_;
169 int32_t xoffset_, yoffset_, length_;
Adam Lesinski21efb682016-09-14 17:35:43 -0700170
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700171 DISALLOW_COPY_AND_ASSIGN(VerticalImageLine);
Adam Lesinski21efb682016-09-14 17:35:43 -0700172};
173
174class DiagonalImageLine {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700175 public:
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700176 explicit DiagonalImageLine(uint8_t** rows, int32_t xoffset, int32_t yoffset,
177 int32_t xstep, int32_t ystep, int32_t length)
178 : rows_(rows),
179 xoffset_(xoffset),
180 yoffset_(yoffset),
181 xstep_(xstep),
182 ystep_(ystep),
183 length_(length) {}
Adam Lesinski21efb682016-09-14 17:35:43 -0700184
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700185 inline int32_t GetLength() const { return length_; }
Adam Lesinski21efb682016-09-14 17:35:43 -0700186
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700187 inline uint32_t GetColor(int32_t idx) const {
188 return NinePatch::PackRGBA(rows_[yoffset_ + (idx * ystep_)] +
189 ((idx + xoffset_) * xstep_) * 4);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700190 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700191
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700192 private:
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700193 uint8_t** rows_;
194 int32_t xoffset_, yoffset_, xstep_, ystep_, length_;
Adam Lesinski21efb682016-09-14 17:35:43 -0700195
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700196 DISALLOW_COPY_AND_ASSIGN(DiagonalImageLine);
Adam Lesinski21efb682016-09-14 17:35:43 -0700197};
198
199class TransparentNeutralColorValidator : public ColorValidator {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700200 public:
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700201 bool IsNeutralColor(uint32_t color) const override {
202 return get_alpha(color) == 0;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700203 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700204};
205
206class WhiteNeutralColorValidator : public ColorValidator {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700207 public:
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700208 bool IsNeutralColor(uint32_t color) const override {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700209 return color == kColorOpaqueWhite;
210 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700211};
212
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700213inline static uint32_t get_alpha(uint32_t color) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700214 return (color & 0xff000000u) >> 24;
Adam Lesinski21efb682016-09-14 17:35:43 -0700215}
216
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700217static bool PopulateBounds(const std::vector<Range>& padding,
218 const std::vector<Range>& layout_bounds,
219 const std::vector<Range>& stretch_regions,
220 const int32_t length, int32_t* padding_start,
221 int32_t* padding_end, int32_t* layout_start,
222 int32_t* layout_end, const StringPiece& edge_name,
223 std::string* out_err) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700224 if (padding.size() > 1) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700225 std::stringstream err_stream;
226 err_stream << "too many padding sections on " << edge_name << " border";
227 *out_err = err_stream.str();
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700228 return false;
229 }
230
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700231 *padding_start = 0;
232 *padding_end = 0;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700233 if (!padding.empty()) {
234 const Range& range = padding.front();
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700235 *padding_start = range.start;
236 *padding_end = length - range.end;
237 } else if (!stretch_regions.empty()) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700238 // No padding was defined. Compute the padding from the first and last
239 // stretch regions.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700240 *padding_start = stretch_regions.front().start;
241 *padding_end = length - stretch_regions.back().end;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700242 }
243
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700244 if (layout_bounds.size() > 2) {
245 std::stringstream err_stream;
246 err_stream << "too many layout bounds sections on " << edge_name
247 << " border";
248 *out_err = err_stream.str();
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700249 return false;
250 }
251
Adam Lesinskice5e56e2016-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 Lesinskice5e56e2016-10-21 17:56:45 -0700260 std::stringstream err_stream;
261 err_stream << "layout bounds on " << edge_name
262 << " border must start at edge";
263 *out_err = err_stream.str();
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700264 return false;
265 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700266 *layout_start = range.end;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700267
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700268 if (layout_bounds.size() >= 2) {
269 const Range& range = layout_bounds.back();
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700270 if (range.end != length) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700271 std::stringstream err_stream;
272 err_stream << "layout bounds on " << edge_name
273 << " border must start at edge";
274 *out_err = err_stream.str();
Adam Lesinski21efb682016-09-14 17:35:43 -0700275 return false;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700276 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700277 *layout_end = length - range.start;
Adam Lesinski21efb682016-09-14 17:35:43 -0700278 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700279 }
280 return true;
Adam Lesinski21efb682016-09-14 17:35:43 -0700281}
282
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700283static int32_t CalculateSegmentCount(const std::vector<Range>& stretch_regions,
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700284 int32_t length) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700285 if (stretch_regions.size() == 0) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700286 return 0;
287 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700288
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700289 const bool start_is_fixed = stretch_regions.front().start != 0;
290 const bool end_is_fixed = stretch_regions.back().end != length;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700291 int32_t modifier = 0;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700292 if (start_is_fixed && end_is_fixed) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700293 modifier = 1;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700294 } else if (!start_is_fixed && !end_is_fixed) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700295 modifier = -1;
296 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700297 return static_cast<int32_t>(stretch_regions.size()) * 2 + modifier;
Adam Lesinski21efb682016-09-14 17:35:43 -0700298}
299
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700300static uint32_t GetRegionColor(uint8_t** rows, const Bounds& region) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700301 // Sample the first pixel to compare against.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700302 const uint32_t expected_color =
303 NinePatch::PackRGBA(rows[region.top] + region.left * 4);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700304 for (int32_t y = region.top; y < region.bottom; y++) {
305 const uint8_t* row = rows[y];
306 for (int32_t x = region.left; x < region.right; x++) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700307 const uint32_t color = NinePatch::PackRGBA(row + x * 4);
308 if (get_alpha(color) == 0) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700309 // The color is transparent.
310 // If the expectedColor is not transparent, NO_COLOR.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700311 if (get_alpha(expected_color) != 0) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700312 return android::Res_png_9patch::NO_COLOR;
Adam Lesinski21efb682016-09-14 17:35:43 -0700313 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700314 } else if (color != expected_color) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700315 return android::Res_png_9patch::NO_COLOR;
316 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700317 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700318 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700319
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700320 if (get_alpha(expected_color) == 0) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700321 return android::Res_png_9patch::TRANSPARENT_COLOR;
322 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700323 return expected_color;
Adam Lesinski21efb682016-09-14 17:35:43 -0700324}
325
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700326// Fills out_colors with each 9-patch section's color. If the whole section is
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700327// transparent,
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700328// it gets the special TRANSPARENT color. If the whole section is the same
329// color, it is assigned
330// that color. Otherwise it gets the special NO_COLOR color.
Adam Lesinski21efb682016-09-14 17:35:43 -0700331//
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700332// Note that the rows contain the 9-patch 1px border, and the indices in the
333// stretch regions are
334// already offset to exclude the border. This means that each time the rows are
335// accessed,
Adam Lesinski21efb682016-09-14 17:35:43 -0700336// the indices must be offset by 1.
337//
338// width and height also include the 9-patch 1px border.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700339static void CalculateRegionColors(
340 uint8_t** rows, const std::vector<Range>& horizontal_stretch_regions,
341 const std::vector<Range>& vertical_stretch_regions, const int32_t width,
342 const int32_t height, std::vector<uint32_t>* out_colors) {
343 int32_t next_top = 0;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700344 Bounds bounds;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700345 auto row_iter = vertical_stretch_regions.begin();
346 while (next_top != height) {
347 if (row_iter != vertical_stretch_regions.end()) {
348 if (next_top != row_iter->start) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700349 // This is a fixed segment.
350 // Offset the bounds by 1 to accommodate the border.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700351 bounds.top = next_top + 1;
352 bounds.bottom = row_iter->start + 1;
353 next_top = row_iter->start;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700354 } else {
355 // This is a stretchy segment.
356 // Offset the bounds by 1 to accommodate the border.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700357 bounds.top = row_iter->start + 1;
358 bounds.bottom = row_iter->end + 1;
359 next_top = row_iter->end;
360 ++row_iter;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700361 }
362 } else {
363 // This is the end, fixed section.
364 // Offset the bounds by 1 to accommodate the border.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700365 bounds.top = next_top + 1;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700366 bounds.bottom = height + 1;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700367 next_top = height;
Adam Lesinski21efb682016-09-14 17:35:43 -0700368 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700369
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700370 int32_t next_left = 0;
371 auto col_iter = horizontal_stretch_regions.begin();
372 while (next_left != width) {
373 if (col_iter != horizontal_stretch_regions.end()) {
374 if (next_left != col_iter->start) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700375 // This is a fixed segment.
376 // Offset the bounds by 1 to accommodate the border.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700377 bounds.left = next_left + 1;
378 bounds.right = col_iter->start + 1;
379 next_left = col_iter->start;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700380 } else {
381 // This is a stretchy segment.
382 // Offset the bounds by 1 to accommodate the border.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700383 bounds.left = col_iter->start + 1;
384 bounds.right = col_iter->end + 1;
385 next_left = col_iter->end;
386 ++col_iter;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700387 }
388 } else {
389 // This is the end, fixed section.
390 // Offset the bounds by 1 to accommodate the border.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700391 bounds.left = next_left + 1;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700392 bounds.right = width + 1;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700393 next_left = width;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700394 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700395 out_colors->push_back(GetRegionColor(rows, bounds));
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700396 }
397 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700398}
399
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700400// Calculates the insets of a row/column of pixels based on where the largest
401// alpha value begins
Adam Lesinski21efb682016-09-14 17:35:43 -0700402// (on both sides).
403template <typename ImageLine>
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700404static void FindOutlineInsets(const ImageLine* image_line, int32_t* out_start,
405 int32_t* out_end) {
406 *out_start = 0;
407 *out_end = 0;
Adam Lesinski21efb682016-09-14 17:35:43 -0700408
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700409 const int32_t length = image_line->GetLength();
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700410 if (length < 3) {
Adam Lesinski21efb682016-09-14 17:35:43 -0700411 return;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700412 }
413
414 // If the length is odd, we want both sides to process the center pixel,
415 // so we use two different midpoints (to account for < and <= in the different
416 // loops).
417 const int32_t mid2 = length / 2;
418 const int32_t mid1 = mid2 + (length % 2);
419
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700420 uint32_t max_alpha = 0;
421 for (int32_t i = 0; i < mid1 && max_alpha != 0xff; i++) {
422 uint32_t alpha = get_alpha(image_line->GetColor(i));
423 if (alpha > max_alpha) {
424 max_alpha = alpha;
425 *out_start = i;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700426 }
427 }
428
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700429 max_alpha = 0;
430 for (int32_t i = length - 1; i >= mid2 && max_alpha != 0xff; i--) {
431 uint32_t alpha = get_alpha(image_line->GetColor(i));
432 if (alpha > max_alpha) {
433 max_alpha = alpha;
434 *out_end = length - (i + 1);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700435 }
436 }
437 return;
Adam Lesinski21efb682016-09-14 17:35:43 -0700438}
439
440template <typename ImageLine>
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700441static uint32_t FindMaxAlpha(const ImageLine* image_line) {
442 const int32_t length = image_line->GetLength();
443 uint32_t max_alpha = 0;
444 for (int32_t idx = 0; idx < length && max_alpha != 0xff; idx++) {
445 uint32_t alpha = get_alpha(image_line->GetColor(idx));
446 if (alpha > max_alpha) {
447 max_alpha = alpha;
Adam Lesinski21efb682016-09-14 17:35:43 -0700448 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700449 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700450 return max_alpha;
Adam Lesinski21efb682016-09-14 17:35:43 -0700451}
452
453// Pack the pixels in as 0xAARRGGBB (as 9-patch expects it).
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700454uint32_t NinePatch::PackRGBA(const uint8_t* pixel) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700455 return (pixel[3] << 24) | (pixel[0] << 16) | (pixel[1] << 8) | pixel[2];
Adam Lesinski21efb682016-09-14 17:35:43 -0700456}
457
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700458std::unique_ptr<NinePatch> NinePatch::Create(uint8_t** rows,
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700459 const int32_t width,
460 const int32_t height,
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700461 std::string* out_err) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700462 if (width < 3 || height < 3) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700463 *out_err = "image must be at least 3x3 (1x1 image with 1 pixel border)";
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700464 return {};
465 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700466
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700467 std::vector<Range> horizontal_padding;
468 std::vector<Range> horizontal_layout_bounds;
469 std::vector<Range> vertical_padding;
470 std::vector<Range> vertical_layout_bounds;
471 std::vector<Range> unexpected_ranges;
472 std::unique_ptr<ColorValidator> color_validator;
Adam Lesinski21efb682016-09-14 17:35:43 -0700473
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700474 if (rows[0][3] == 0) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700475 color_validator = util::make_unique<TransparentNeutralColorValidator>();
476 } else if (PackRGBA(rows[0]) == kColorOpaqueWhite) {
477 color_validator = util::make_unique<WhiteNeutralColorValidator>();
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700478 } else {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700479 *out_err =
480 "top-left corner pixel must be either opaque white or transparent";
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700481 return {};
482 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700483
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700484 // Private constructor, can't use make_unique.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700485 auto nine_patch = std::unique_ptr<NinePatch>(new NinePatch());
Adam Lesinski21efb682016-09-14 17:35:43 -0700486
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700487 HorizontalImageLine top_row(rows, 0, 0, width);
488 if (!FillRanges(&top_row, color_validator.get(),
489 &nine_patch->horizontal_stretch_regions, &unexpected_ranges,
490 out_err)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700491 return {};
492 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700493
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700494 if (!unexpected_ranges.empty()) {
495 const Range& range = unexpected_ranges[0];
496 std::stringstream err_stream;
497 err_stream << "found unexpected optical bounds (red pixel) on top border "
498 << "at x=" << range.start + 1;
499 *out_err = err_stream.str();
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700500 return {};
501 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700502
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700503 VerticalImageLine left_col(rows, 0, 0, height);
504 if (!FillRanges(&left_col, color_validator.get(),
505 &nine_patch->vertical_stretch_regions, &unexpected_ranges,
506 out_err)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700507 return {};
508 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700509
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700510 if (!unexpected_ranges.empty()) {
511 const Range& range = unexpected_ranges[0];
512 std::stringstream err_stream;
513 err_stream << "found unexpected optical bounds (red pixel) on left border "
514 << "at y=" << range.start + 1;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700515 return {};
516 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700517
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700518 HorizontalImageLine bottom_row(rows, 0, height - 1, width);
519 if (!FillRanges(&bottom_row, color_validator.get(), &horizontal_padding,
520 &horizontal_layout_bounds, out_err)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700521 return {};
522 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700523
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700524 if (!PopulateBounds(horizontal_padding, horizontal_layout_bounds,
525 nine_patch->horizontal_stretch_regions, width - 2,
526 &nine_patch->padding.left, &nine_patch->padding.right,
527 &nine_patch->layout_bounds.left,
528 &nine_patch->layout_bounds.right, "bottom", out_err)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700529 return {};
530 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700531
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700532 VerticalImageLine right_col(rows, width - 1, 0, height);
533 if (!FillRanges(&right_col, color_validator.get(), &vertical_padding,
534 &vertical_layout_bounds, out_err)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700535 return {};
536 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700537
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700538 if (!PopulateBounds(vertical_padding, vertical_layout_bounds,
539 nine_patch->vertical_stretch_regions, height - 2,
540 &nine_patch->padding.top, &nine_patch->padding.bottom,
541 &nine_patch->layout_bounds.top,
542 &nine_patch->layout_bounds.bottom, "right", out_err)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700543 return {};
544 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700545
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700546 // Fill the region colors of the 9-patch.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700547 const int32_t num_rows =
548 CalculateSegmentCount(nine_patch->horizontal_stretch_regions, width - 2);
549 const int32_t num_cols =
550 CalculateSegmentCount(nine_patch->vertical_stretch_regions, height - 2);
551 if ((int64_t)num_rows * (int64_t)num_cols > 0x7f) {
552 *out_err = "too many regions in 9-patch";
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700553 return {};
554 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700555
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700556 nine_patch->region_colors.reserve(num_rows * num_cols);
557 CalculateRegionColors(rows, nine_patch->horizontal_stretch_regions,
558 nine_patch->vertical_stretch_regions, width - 2,
559 height - 2, &nine_patch->region_colors);
Adam Lesinski21efb682016-09-14 17:35:43 -0700560
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700561 // Compute the outline based on opacity.
Adam Lesinski21efb682016-09-14 17:35:43 -0700562
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700563 // Find left and right extent of 9-patch content on center row.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700564 HorizontalImageLine mid_row(rows, 1, height / 2, width - 2);
565 FindOutlineInsets(&mid_row, &nine_patch->outline.left,
566 &nine_patch->outline.right);
Adam Lesinski21efb682016-09-14 17:35:43 -0700567
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700568 // Find top and bottom extent of 9-patch content on center column.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700569 VerticalImageLine mid_col(rows, width / 2, 1, height - 2);
570 FindOutlineInsets(&mid_col, &nine_patch->outline.top,
571 &nine_patch->outline.bottom);
Adam Lesinski21efb682016-09-14 17:35:43 -0700572
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700573 const int32_t outline_width =
574 (width - 2) - nine_patch->outline.left - nine_patch->outline.right;
575 const int32_t outline_height =
576 (height - 2) - nine_patch->outline.top - nine_patch->outline.bottom;
Adam Lesinski21efb682016-09-14 17:35:43 -0700577
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700578 // Find the largest alpha value within the outline area.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700579 HorizontalImageLine outline_mid_row(
580 rows, 1 + nine_patch->outline.left,
581 1 + nine_patch->outline.top + (outline_height / 2), outline_width);
582 VerticalImageLine outline_mid_col(
583 rows, 1 + nine_patch->outline.left + (outline_width / 2),
584 1 + nine_patch->outline.top, outline_height);
585 nine_patch->outline_alpha =
586 std::max(FindMaxAlpha(&outline_mid_row), FindMaxAlpha(&outline_mid_col));
Adam Lesinski21efb682016-09-14 17:35:43 -0700587
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700588 // Assuming the image is a round rect, compute the radius by marching
589 // diagonally from the top left corner towards the center.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700590 DiagonalImageLine diagonal(rows, 1 + nine_patch->outline.left,
591 1 + nine_patch->outline.top, 1, 1,
592 std::min(outline_width, outline_height));
593 int32_t top_left, bottom_right;
594 FindOutlineInsets(&diagonal, &top_left, &bottom_right);
Adam Lesinski21efb682016-09-14 17:35:43 -0700595
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700596 /* Determine source radius based upon inset:
597 * sqrt(r^2 + r^2) = sqrt(i^2 + i^2) + r
598 * sqrt(2) * r = sqrt(2) * i + r
599 * (sqrt(2) - 1) * r = sqrt(2) * i
600 * r = sqrt(2) / (sqrt(2) - 1) * i
601 */
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700602 nine_patch->outline_radius = 3.4142f * top_left;
603 return nine_patch;
Adam Lesinski21efb682016-09-14 17:35:43 -0700604}
605
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700606std::unique_ptr<uint8_t[]> NinePatch::SerializeBase(size_t* outLen) const {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700607 android::Res_png_9patch data;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700608 data.numXDivs = static_cast<uint8_t>(horizontal_stretch_regions.size()) * 2;
609 data.numYDivs = static_cast<uint8_t>(vertical_stretch_regions.size()) * 2;
610 data.numColors = static_cast<uint8_t>(region_colors.size());
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700611 data.paddingLeft = padding.left;
612 data.paddingRight = padding.right;
613 data.paddingTop = padding.top;
614 data.paddingBottom = padding.bottom;
Adam Lesinski21efb682016-09-14 17:35:43 -0700615
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700616 auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[data.serializedSize()]);
617 android::Res_png_9patch::serialize(
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700618 data, (const int32_t*)horizontal_stretch_regions.data(),
619 (const int32_t*)vertical_stretch_regions.data(), region_colors.data(),
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700620 buffer.get());
621 // Convert to file endianness.
622 reinterpret_cast<android::Res_png_9patch*>(buffer.get())->deviceToFile();
Adam Lesinskiedba9412016-10-04 17:33:04 -0700623
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700624 *outLen = data.serializedSize();
625 return buffer;
Adam Lesinski21efb682016-09-14 17:35:43 -0700626}
627
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700628std::unique_ptr<uint8_t[]> NinePatch::SerializeLayoutBounds(
629 size_t* out_len) const {
630 size_t chunk_len = sizeof(uint32_t) * 4;
631 auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[chunk_len]);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700632 uint8_t* cursor = buffer.get();
Adam Lesinski21efb682016-09-14 17:35:43 -0700633
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700634 memcpy(cursor, &layout_bounds.left, sizeof(layout_bounds.left));
635 cursor += sizeof(layout_bounds.left);
Adam Lesinski21efb682016-09-14 17:35:43 -0700636
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700637 memcpy(cursor, &layout_bounds.top, sizeof(layout_bounds.top));
638 cursor += sizeof(layout_bounds.top);
Adam Lesinski21efb682016-09-14 17:35:43 -0700639
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700640 memcpy(cursor, &layout_bounds.right, sizeof(layout_bounds.right));
641 cursor += sizeof(layout_bounds.right);
Adam Lesinski21efb682016-09-14 17:35:43 -0700642
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700643 memcpy(cursor, &layout_bounds.bottom, sizeof(layout_bounds.bottom));
644 cursor += sizeof(layout_bounds.bottom);
Adam Lesinski21efb682016-09-14 17:35:43 -0700645
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700646 *out_len = chunk_len;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700647 return buffer;
Adam Lesinski21efb682016-09-14 17:35:43 -0700648}
649
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700650std::unique_ptr<uint8_t[]> NinePatch::SerializeRoundedRectOutline(
651 size_t* out_len) const {
652 size_t chunk_len = sizeof(uint32_t) * 6;
653 auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[chunk_len]);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700654 uint8_t* cursor = buffer.get();
Adam Lesinski21efb682016-09-14 17:35:43 -0700655
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700656 memcpy(cursor, &outline.left, sizeof(outline.left));
657 cursor += sizeof(outline.left);
Adam Lesinski21efb682016-09-14 17:35:43 -0700658
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700659 memcpy(cursor, &outline.top, sizeof(outline.top));
660 cursor += sizeof(outline.top);
Adam Lesinski21efb682016-09-14 17:35:43 -0700661
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700662 memcpy(cursor, &outline.right, sizeof(outline.right));
663 cursor += sizeof(outline.right);
Adam Lesinski21efb682016-09-14 17:35:43 -0700664
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700665 memcpy(cursor, &outline.bottom, sizeof(outline.bottom));
666 cursor += sizeof(outline.bottom);
Adam Lesinski21efb682016-09-14 17:35:43 -0700667
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700668 *((float*)cursor) = outline_radius;
669 cursor += sizeof(outline_radius);
Adam Lesinski21efb682016-09-14 17:35:43 -0700670
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700671 *((uint32_t*)cursor) = outline_alpha;
Adam Lesinski21efb682016-09-14 17:35:43 -0700672
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700673 *out_len = chunk_len;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700674 return buffer;
Adam Lesinski21efb682016-09-14 17:35:43 -0700675}
676
677::std::ostream& operator<<(::std::ostream& out, const Range& range) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700678 return out << "[" << range.start << ", " << range.end << ")";
Adam Lesinski21efb682016-09-14 17:35:43 -0700679}
680
681::std::ostream& operator<<(::std::ostream& out, const Bounds& bounds) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700682 return out << "l=" << bounds.left << " t=" << bounds.top
683 << " r=" << bounds.right << " b=" << bounds.bottom;
Adam Lesinski21efb682016-09-14 17:35:43 -0700684}
685
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700686::std::ostream& operator<<(::std::ostream& out, const NinePatch& nine_patch) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700687 return out << "horizontalStretch:"
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700688 << util::Joiner(nine_patch.horizontal_stretch_regions, " ")
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700689 << " verticalStretch:"
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700690 << util::Joiner(nine_patch.vertical_stretch_regions, " ")
691 << " padding: " << nine_patch.padding
692 << ", bounds: " << nine_patch.layout_bounds
693 << ", outline: " << nine_patch.outline
694 << " rad=" << nine_patch.outline_radius
695 << " alpha=" << nine_patch.outline_alpha;
Adam Lesinski21efb682016-09-14 17:35:43 -0700696}
697
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700698} // namespace aapt