blob: 8842eb70f3a0983789e3f56d426cedea339bbe59 [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"
18#include "util/StringPiece.h"
19#include "util/Util.h"
20
21#include <androidfw/ResourceTypes.h>
22#include <sstream>
23#include <string>
24#include <vector>
25
26namespace aapt {
27
28// Colors in the format 0xAARRGGBB (the way 9-patch expects it).
29constexpr static const uint32_t kColorOpaqueWhite = 0xffffffffu;
30constexpr static const uint32_t kColorOpaqueBlack = 0xff000000u;
Adam Lesinskicacb28f2016-10-19 12:18:14 -070031constexpr static const uint32_t kColorOpaqueRed = 0xffff0000u;
Adam Lesinski21efb682016-09-14 17:35:43 -070032
33constexpr static const uint32_t kPrimaryColor = kColorOpaqueBlack;
34constexpr static const uint32_t kSecondaryColor = kColorOpaqueRed;
35
36/**
37 * Returns the alpha value encoded in the 0xAARRGBB encoded pixel.
38 */
39static uint32_t getAlpha(uint32_t color);
40
41/**
42 * Determines whether a color on an ImageLine is valid.
43 * A 9patch image may use a transparent color as neutral,
44 * or a fully opaque white color as neutral, based on the
45 * pixel color at (0,0) of the image. One or the other is fine,
46 * but we need to ensure consistency throughout the image.
47 */
48class ColorValidator {
Adam Lesinskicacb28f2016-10-19 12:18:14 -070049 public:
50 virtual ~ColorValidator() = default;
Adam Lesinski21efb682016-09-14 17:35:43 -070051
Adam Lesinskicacb28f2016-10-19 12:18:14 -070052 /**
53 * Returns true if the color specified is a neutral color
54 * (no padding, stretching, or optical bounds).
55 */
56 virtual bool isNeutralColor(uint32_t color) const = 0;
Adam Lesinski21efb682016-09-14 17:35:43 -070057
Adam Lesinskicacb28f2016-10-19 12:18:14 -070058 /**
59 * Returns true if the color is either a neutral color
60 * or one denoting padding, stretching, or optical bounds.
61 */
62 bool isValidColor(uint32_t color) const {
63 switch (color) {
64 case kPrimaryColor:
65 case kSecondaryColor:
66 return true;
Adam Lesinski21efb682016-09-14 17:35:43 -070067 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -070068 return isNeutralColor(color);
69 }
Adam Lesinski21efb682016-09-14 17:35:43 -070070};
71
72// Walks an ImageLine and records Ranges of primary and secondary colors.
Adam Lesinskicacb28f2016-10-19 12:18:14 -070073// The primary color is black and is used to denote a padding or stretching
74// range,
Adam Lesinski21efb682016-09-14 17:35:43 -070075// depending on which border we're iterating over.
76// The secondary color is red and is used to denote optical bounds.
77//
Adam Lesinskicacb28f2016-10-19 12:18:14 -070078// An ImageLine is a templated-interface that would look something like this if
79// it
Adam Lesinski21efb682016-09-14 17:35:43 -070080// were polymorphic:
81//
82// class ImageLine {
83// public:
84// virtual int32_t getLength() const = 0;
85// virtual uint32_t getColor(int32_t idx) const = 0;
86// };
87//
88template <typename ImageLine>
89static bool fillRanges(const ImageLine* imageLine,
90 const ColorValidator* colorValidator,
91 std::vector<Range>* primaryRanges,
Adam Lesinskicacb28f2016-10-19 12:18:14 -070092 std::vector<Range>* secondaryRanges, std::string* err) {
93 const int32_t length = imageLine->getLength();
Adam Lesinski21efb682016-09-14 17:35:43 -070094
Adam Lesinskicacb28f2016-10-19 12:18:14 -070095 uint32_t lastColor = 0xffffffffu;
96 for (int32_t idx = 1; idx < length - 1; idx++) {
97 const uint32_t color = imageLine->getColor(idx);
98 if (!colorValidator->isValidColor(color)) {
99 *err = "found an invalid color";
100 return false;
Adam Lesinski21efb682016-09-14 17:35:43 -0700101 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700102
103 if (color != lastColor) {
104 // We are ending a range. Which range?
105 // note: encode the x offset without the final 1 pixel border.
106 if (lastColor == kPrimaryColor) {
107 primaryRanges->back().end = idx - 1;
108 } else if (lastColor == kSecondaryColor) {
109 secondaryRanges->back().end = idx - 1;
110 }
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) {
115 primaryRanges->push_back(Range(idx - 1, length - 2));
116 } else if (color == kSecondaryColor) {
117 secondaryRanges->push_back(Range(idx - 1, length - 2));
118 }
119 lastColor = color;
120 }
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:
131 explicit HorizontalImageLine(uint8_t** rows, int32_t xOffset, int32_t yOffset,
132 int32_t length)
133 : mRows(rows), mXOffset(xOffset), mYOffset(yOffset), mLength(length) {}
Adam Lesinski21efb682016-09-14 17:35:43 -0700134
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700135 inline int32_t getLength() const { return mLength; }
Adam Lesinski21efb682016-09-14 17:35:43 -0700136
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700137 inline uint32_t getColor(int32_t idx) const {
138 return NinePatch::packRGBA(mRows[mYOffset] + (idx + mXOffset) * 4);
139 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700140
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700141 private:
142 uint8_t** mRows;
143 int32_t mXOffset, mYOffset, mLength;
Adam Lesinski21efb682016-09-14 17:35:43 -0700144
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700145 DISALLOW_COPY_AND_ASSIGN(HorizontalImageLine);
Adam Lesinski21efb682016-09-14 17:35:43 -0700146};
147
148/**
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700149 * Iterates over a column in an image. Implements the templated ImageLine
150 * interface.
Adam Lesinski21efb682016-09-14 17:35:43 -0700151 */
152class VerticalImageLine {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700153 public:
154 explicit VerticalImageLine(uint8_t** rows, int32_t xOffset, int32_t yOffset,
155 int32_t length)
156 : mRows(rows), mXOffset(xOffset), mYOffset(yOffset), mLength(length) {}
Adam Lesinski21efb682016-09-14 17:35:43 -0700157
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700158 inline int32_t getLength() const { return mLength; }
Adam Lesinski21efb682016-09-14 17:35:43 -0700159
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700160 inline uint32_t getColor(int32_t idx) const {
161 return NinePatch::packRGBA(mRows[mYOffset + idx] + (mXOffset * 4));
162 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700163
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700164 private:
165 uint8_t** mRows;
166 int32_t mXOffset, mYOffset, mLength;
Adam Lesinski21efb682016-09-14 17:35:43 -0700167
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700168 DISALLOW_COPY_AND_ASSIGN(VerticalImageLine);
Adam Lesinski21efb682016-09-14 17:35:43 -0700169};
170
171class DiagonalImageLine {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700172 public:
173 explicit DiagonalImageLine(uint8_t** rows, int32_t xOffset, int32_t yOffset,
174 int32_t xStep, int32_t yStep, int32_t length)
175 : mRows(rows),
176 mXOffset(xOffset),
177 mYOffset(yOffset),
178 mXStep(xStep),
179 mYStep(yStep),
180 mLength(length) {}
Adam Lesinski21efb682016-09-14 17:35:43 -0700181
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700182 inline int32_t getLength() const { return mLength; }
Adam Lesinski21efb682016-09-14 17:35:43 -0700183
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700184 inline uint32_t getColor(int32_t idx) const {
185 return NinePatch::packRGBA(mRows[mYOffset + (idx * mYStep)] +
186 ((idx + mXOffset) * mXStep) * 4);
187 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700188
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700189 private:
190 uint8_t** mRows;
191 int32_t mXOffset, mYOffset, mXStep, mYStep, mLength;
Adam Lesinski21efb682016-09-14 17:35:43 -0700192
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700193 DISALLOW_COPY_AND_ASSIGN(DiagonalImageLine);
Adam Lesinski21efb682016-09-14 17:35:43 -0700194};
195
196class TransparentNeutralColorValidator : public ColorValidator {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700197 public:
198 bool isNeutralColor(uint32_t color) const override {
199 return getAlpha(color) == 0;
200 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700201};
202
203class WhiteNeutralColorValidator : public ColorValidator {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700204 public:
205 bool isNeutralColor(uint32_t color) const override {
206 return color == kColorOpaqueWhite;
207 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700208};
209
210inline static uint32_t getAlpha(uint32_t color) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700211 return (color & 0xff000000u) >> 24;
Adam Lesinski21efb682016-09-14 17:35:43 -0700212}
213
214static bool populateBounds(const std::vector<Range>& padding,
215 const std::vector<Range>& layoutBounds,
216 const std::vector<Range>& stretchRegions,
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700217 const int32_t length, int32_t* paddingStart,
218 int32_t* paddingEnd, int32_t* layoutStart,
219 int32_t* layoutEnd, const StringPiece& edgeName,
Adam Lesinski21efb682016-09-14 17:35:43 -0700220 std::string* err) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700221 if (padding.size() > 1) {
222 std::stringstream errStream;
223 errStream << "too many padding sections on " << edgeName << " border";
224 *err = errStream.str();
225 return false;
226 }
227
228 *paddingStart = 0;
229 *paddingEnd = 0;
230 if (!padding.empty()) {
231 const Range& range = padding.front();
232 *paddingStart = range.start;
233 *paddingEnd = length - range.end;
234 } else if (!stretchRegions.empty()) {
235 // No padding was defined. Compute the padding from the first and last
236 // stretch regions.
237 *paddingStart = stretchRegions.front().start;
238 *paddingEnd = length - stretchRegions.back().end;
239 }
240
241 if (layoutBounds.size() > 2) {
242 std::stringstream errStream;
243 errStream << "too many layout bounds sections on " << edgeName << " border";
244 *err = errStream.str();
245 return false;
246 }
247
248 *layoutStart = 0;
249 *layoutEnd = 0;
250 if (layoutBounds.size() >= 1) {
251 const Range& range = layoutBounds.front();
252 // If there is only one layout bound segment, it might not start at 0, but
253 // then it should
254 // end at length.
255 if (range.start != 0 && range.end != length) {
256 std::stringstream errStream;
257 errStream << "layout bounds on " << edgeName
258 << " border must start at edge";
259 *err = errStream.str();
260 return false;
261 }
262 *layoutStart = range.end;
263
264 if (layoutBounds.size() >= 2) {
265 const Range& range = layoutBounds.back();
266 if (range.end != length) {
Adam Lesinski21efb682016-09-14 17:35:43 -0700267 std::stringstream errStream;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700268 errStream << "layout bounds on " << edgeName
269 << " border must start at edge";
Adam Lesinski21efb682016-09-14 17:35:43 -0700270 *err = errStream.str();
271 return false;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700272 }
273 *layoutEnd = length - range.start;
Adam Lesinski21efb682016-09-14 17:35:43 -0700274 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700275 }
276 return true;
Adam Lesinski21efb682016-09-14 17:35:43 -0700277}
278
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700279static int32_t calculateSegmentCount(const std::vector<Range>& stretchRegions,
280 int32_t length) {
281 if (stretchRegions.size() == 0) {
282 return 0;
283 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700284
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700285 const bool startIsFixed = stretchRegions.front().start != 0;
286 const bool endIsFixed = stretchRegions.back().end != length;
287 int32_t modifier = 0;
288 if (startIsFixed && endIsFixed) {
289 modifier = 1;
290 } else if (!startIsFixed && !endIsFixed) {
291 modifier = -1;
292 }
293 return static_cast<int32_t>(stretchRegions.size()) * 2 + modifier;
Adam Lesinski21efb682016-09-14 17:35:43 -0700294}
295
296static uint32_t getRegionColor(uint8_t** rows, const Bounds& region) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700297 // Sample the first pixel to compare against.
298 const uint32_t expectedColor =
299 NinePatch::packRGBA(rows[region.top] + region.left * 4);
300 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++) {
303 const uint32_t color = NinePatch::packRGBA(row + x * 4);
304 if (getAlpha(color) == 0) {
305 // The color is transparent.
306 // If the expectedColor is not transparent, NO_COLOR.
307 if (getAlpha(expectedColor) != 0) {
308 return android::Res_png_9patch::NO_COLOR;
Adam Lesinski21efb682016-09-14 17:35:43 -0700309 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700310 } else if (color != expectedColor) {
311 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 Lesinskicacb28f2016-10-19 12:18:14 -0700316 if (getAlpha(expectedColor) == 0) {
317 return android::Res_png_9patch::TRANSPARENT_COLOR;
318 }
319 return expectedColor;
Adam Lesinski21efb682016-09-14 17:35:43 -0700320}
321
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700322// Fills outColors with each 9-patch section's colour. If the whole section is
323// transparent,
324// it gets the special TRANSPARENT colour. If the whole section is the same
325// colour, it is assigned
Adam Lesinski21efb682016-09-14 17:35:43 -0700326// that colour. Otherwise it gets the special NO_COLOR colour.
327//
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.
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700335static void calculateRegionColors(
336 uint8_t** rows, const std::vector<Range>& horizontalStretchRegions,
337 const std::vector<Range>& verticalStretchRegions, const int32_t width,
338 const int32_t height, std::vector<uint32_t>* outColors) {
339 int32_t nextTop = 0;
340 Bounds bounds;
341 auto rowIter = verticalStretchRegions.begin();
342 while (nextTop != height) {
343 if (rowIter != verticalStretchRegions.end()) {
344 if (nextTop != rowIter->start) {
345 // This is a fixed segment.
346 // Offset the bounds by 1 to accommodate the border.
347 bounds.top = nextTop + 1;
348 bounds.bottom = rowIter->start + 1;
349 nextTop = rowIter->start;
350 } else {
351 // This is a stretchy segment.
352 // Offset the bounds by 1 to accommodate the border.
353 bounds.top = rowIter->start + 1;
354 bounds.bottom = rowIter->end + 1;
355 nextTop = rowIter->end;
356 ++rowIter;
357 }
358 } else {
359 // This is the end, fixed section.
360 // Offset the bounds by 1 to accommodate the border.
361 bounds.top = nextTop + 1;
362 bounds.bottom = height + 1;
363 nextTop = height;
Adam Lesinski21efb682016-09-14 17:35:43 -0700364 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700365
366 int32_t nextLeft = 0;
367 auto colIter = horizontalStretchRegions.begin();
368 while (nextLeft != width) {
369 if (colIter != horizontalStretchRegions.end()) {
370 if (nextLeft != colIter->start) {
371 // This is a fixed segment.
372 // Offset the bounds by 1 to accommodate the border.
373 bounds.left = nextLeft + 1;
374 bounds.right = colIter->start + 1;
375 nextLeft = colIter->start;
376 } else {
377 // This is a stretchy segment.
378 // Offset the bounds by 1 to accommodate the border.
379 bounds.left = colIter->start + 1;
380 bounds.right = colIter->end + 1;
381 nextLeft = colIter->end;
382 ++colIter;
383 }
384 } else {
385 // This is the end, fixed section.
386 // Offset the bounds by 1 to accommodate the border.
387 bounds.left = nextLeft + 1;
388 bounds.right = width + 1;
389 nextLeft = width;
390 }
391 outColors->push_back(getRegionColor(rows, bounds));
392 }
393 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700394}
395
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700396// Calculates the insets of a row/column of pixels based on where the largest
397// alpha value begins
Adam Lesinski21efb682016-09-14 17:35:43 -0700398// (on both sides).
399template <typename ImageLine>
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700400static void findOutlineInsets(const ImageLine* imageLine, int32_t* outStart,
401 int32_t* outEnd) {
402 *outStart = 0;
403 *outEnd = 0;
Adam Lesinski21efb682016-09-14 17:35:43 -0700404
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700405 const int32_t length = imageLine->getLength();
406 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
416 uint32_t maxAlpha = 0;
417 for (int32_t i = 0; i < mid1 && maxAlpha != 0xff; i++) {
418 uint32_t alpha = getAlpha(imageLine->getColor(i));
419 if (alpha > maxAlpha) {
420 maxAlpha = alpha;
421 *outStart = i;
422 }
423 }
424
425 maxAlpha = 0;
426 for (int32_t i = length - 1; i >= mid2 && maxAlpha != 0xff; i--) {
427 uint32_t alpha = getAlpha(imageLine->getColor(i));
428 if (alpha > maxAlpha) {
429 maxAlpha = alpha;
430 *outEnd = length - (i + 1);
431 }
432 }
433 return;
Adam Lesinski21efb682016-09-14 17:35:43 -0700434}
435
436template <typename ImageLine>
437static uint32_t findMaxAlpha(const ImageLine* imageLine) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700438 const int32_t length = imageLine->getLength();
439 uint32_t maxAlpha = 0;
440 for (int32_t idx = 0; idx < length && maxAlpha != 0xff; idx++) {
441 uint32_t alpha = getAlpha(imageLine->getColor(idx));
442 if (alpha > maxAlpha) {
443 maxAlpha = alpha;
Adam Lesinski21efb682016-09-14 17:35:43 -0700444 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700445 }
446 return maxAlpha;
Adam Lesinski21efb682016-09-14 17:35:43 -0700447}
448
449// Pack the pixels in as 0xAARRGGBB (as 9-patch expects it).
450uint32_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
454std::unique_ptr<NinePatch> NinePatch::create(uint8_t** rows,
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700455 const int32_t width,
456 const int32_t height,
Adam Lesinski21efb682016-09-14 17:35:43 -0700457 std::string* err) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700458 if (width < 3 || height < 3) {
459 *err = "image must be at least 3x3 (1x1 image with 1 pixel border)";
460 return {};
461 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700462
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700463 std::vector<Range> horizontalPadding;
464 std::vector<Range> horizontalOpticalBounds;
465 std::vector<Range> verticalPadding;
466 std::vector<Range> verticalOpticalBounds;
467 std::vector<Range> unexpectedRanges;
468 std::unique_ptr<ColorValidator> colorValidator;
Adam Lesinski21efb682016-09-14 17:35:43 -0700469
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700470 if (rows[0][3] == 0) {
471 colorValidator = util::make_unique<TransparentNeutralColorValidator>();
472 } else if (packRGBA(rows[0]) == kColorOpaqueWhite) {
473 colorValidator = util::make_unique<WhiteNeutralColorValidator>();
474 } else {
475 *err = "top-left corner pixel must be either opaque white or transparent";
476 return {};
477 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700478
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700479 // Private constructor, can't use make_unique.
480 auto ninePatch = std::unique_ptr<NinePatch>(new NinePatch());
Adam Lesinski21efb682016-09-14 17:35:43 -0700481
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700482 HorizontalImageLine topRow(rows, 0, 0, width);
483 if (!fillRanges(&topRow, colorValidator.get(),
484 &ninePatch->horizontalStretchRegions, &unexpectedRanges,
485 err)) {
486 return {};
487 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700488
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700489 if (!unexpectedRanges.empty()) {
490 const Range& range = unexpectedRanges[0];
491 std::stringstream errStream;
492 errStream << "found unexpected optical bounds (red pixel) on top border "
493 << "at x=" << range.start + 1;
494 *err = errStream.str();
495 return {};
496 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700497
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700498 VerticalImageLine leftCol(rows, 0, 0, height);
499 if (!fillRanges(&leftCol, colorValidator.get(),
500 &ninePatch->verticalStretchRegions, &unexpectedRanges, err)) {
501 return {};
502 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700503
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700504 if (!unexpectedRanges.empty()) {
505 const Range& range = unexpectedRanges[0];
506 std::stringstream errStream;
507 errStream << "found unexpected optical bounds (red pixel) on left border "
508 << "at y=" << range.start + 1;
509 return {};
510 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700511
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700512 HorizontalImageLine bottomRow(rows, 0, height - 1, width);
513 if (!fillRanges(&bottomRow, colorValidator.get(), &horizontalPadding,
514 &horizontalOpticalBounds, err)) {
515 return {};
516 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700517
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700518 if (!populateBounds(horizontalPadding, horizontalOpticalBounds,
519 ninePatch->horizontalStretchRegions, width - 2,
520 &ninePatch->padding.left, &ninePatch->padding.right,
521 &ninePatch->layoutBounds.left,
522 &ninePatch->layoutBounds.right, "bottom", err)) {
523 return {};
524 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700525
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700526 VerticalImageLine rightCol(rows, width - 1, 0, height);
527 if (!fillRanges(&rightCol, colorValidator.get(), &verticalPadding,
528 &verticalOpticalBounds, err)) {
529 return {};
530 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700531
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700532 if (!populateBounds(verticalPadding, verticalOpticalBounds,
533 ninePatch->verticalStretchRegions, height - 2,
534 &ninePatch->padding.top, &ninePatch->padding.bottom,
535 &ninePatch->layoutBounds.top,
536 &ninePatch->layoutBounds.bottom, "right", err)) {
537 return {};
538 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700539
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700540 // Fill the region colors of the 9-patch.
541 const int32_t numRows =
542 calculateSegmentCount(ninePatch->horizontalStretchRegions, width - 2);
543 const int32_t numCols =
544 calculateSegmentCount(ninePatch->verticalStretchRegions, height - 2);
545 if ((int64_t)numRows * (int64_t)numCols > 0x7f) {
546 *err = "too many regions in 9-patch";
547 return {};
548 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700549
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700550 ninePatch->regionColors.reserve(numRows * numCols);
551 calculateRegionColors(rows, ninePatch->horizontalStretchRegions,
552 ninePatch->verticalStretchRegions, width - 2,
553 height - 2, &ninePatch->regionColors);
Adam Lesinski21efb682016-09-14 17:35:43 -0700554
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700555 // Compute the outline based on opacity.
Adam Lesinski21efb682016-09-14 17:35:43 -0700556
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700557 // Find left and right extent of 9-patch content on center row.
558 HorizontalImageLine midRow(rows, 1, height / 2, width - 2);
559 findOutlineInsets(&midRow, &ninePatch->outline.left,
560 &ninePatch->outline.right);
Adam Lesinski21efb682016-09-14 17:35:43 -0700561
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700562 // Find top and bottom extent of 9-patch content on center column.
563 VerticalImageLine midCol(rows, width / 2, 1, height - 2);
564 findOutlineInsets(&midCol, &ninePatch->outline.top,
565 &ninePatch->outline.bottom);
Adam Lesinski21efb682016-09-14 17:35:43 -0700566
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700567 const int32_t outlineWidth =
568 (width - 2) - ninePatch->outline.left - ninePatch->outline.right;
569 const int32_t outlineHeight =
570 (height - 2) - ninePatch->outline.top - ninePatch->outline.bottom;
Adam Lesinski21efb682016-09-14 17:35:43 -0700571
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700572 // Find the largest alpha value within the outline area.
573 HorizontalImageLine outlineMidRow(
574 rows, 1 + ninePatch->outline.left,
575 1 + ninePatch->outline.top + (outlineHeight / 2), outlineWidth);
576 VerticalImageLine outlineMidCol(
577 rows, 1 + ninePatch->outline.left + (outlineWidth / 2),
578 1 + ninePatch->outline.top, outlineHeight);
579 ninePatch->outlineAlpha =
580 std::max(findMaxAlpha(&outlineMidRow), findMaxAlpha(&outlineMidCol));
Adam Lesinski21efb682016-09-14 17:35:43 -0700581
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700582 // Assuming the image is a round rect, compute the radius by marching
583 // diagonally from the top left corner towards the center.
584 DiagonalImageLine diagonal(rows, 1 + ninePatch->outline.left,
585 1 + ninePatch->outline.top, 1, 1,
586 std::min(outlineWidth, outlineHeight));
587 int32_t topLeft, bottomRight;
588 findOutlineInsets(&diagonal, &topLeft, &bottomRight);
Adam Lesinski21efb682016-09-14 17:35:43 -0700589
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700590 /* Determine source radius based upon inset:
591 * sqrt(r^2 + r^2) = sqrt(i^2 + i^2) + r
592 * sqrt(2) * r = sqrt(2) * i + r
593 * (sqrt(2) - 1) * r = sqrt(2) * i
594 * r = sqrt(2) / (sqrt(2) - 1) * i
595 */
596 ninePatch->outlineRadius = 3.4142f * topLeft;
597 return ninePatch;
Adam Lesinski21efb682016-09-14 17:35:43 -0700598}
599
600std::unique_ptr<uint8_t[]> NinePatch::serializeBase(size_t* outLen) const {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700601 android::Res_png_9patch data;
602 data.numXDivs = static_cast<uint8_t>(horizontalStretchRegions.size()) * 2;
603 data.numYDivs = static_cast<uint8_t>(verticalStretchRegions.size()) * 2;
604 data.numColors = static_cast<uint8_t>(regionColors.size());
605 data.paddingLeft = padding.left;
606 data.paddingRight = padding.right;
607 data.paddingTop = padding.top;
608 data.paddingBottom = padding.bottom;
Adam Lesinski21efb682016-09-14 17:35:43 -0700609
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700610 auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[data.serializedSize()]);
611 android::Res_png_9patch::serialize(
612 data, (const int32_t*)horizontalStretchRegions.data(),
613 (const int32_t*)verticalStretchRegions.data(), regionColors.data(),
614 buffer.get());
615 // Convert to file endianness.
616 reinterpret_cast<android::Res_png_9patch*>(buffer.get())->deviceToFile();
Adam Lesinskiedba9412016-10-04 17:33:04 -0700617
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700618 *outLen = data.serializedSize();
619 return buffer;
Adam Lesinski21efb682016-09-14 17:35:43 -0700620}
621
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700622std::unique_ptr<uint8_t[]> NinePatch::serializeLayoutBounds(
623 size_t* outLen) const {
624 size_t chunkLen = sizeof(uint32_t) * 4;
625 auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[chunkLen]);
626 uint8_t* cursor = buffer.get();
Adam Lesinski21efb682016-09-14 17:35:43 -0700627
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700628 memcpy(cursor, &layoutBounds.left, sizeof(layoutBounds.left));
629 cursor += sizeof(layoutBounds.left);
Adam Lesinski21efb682016-09-14 17:35:43 -0700630
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700631 memcpy(cursor, &layoutBounds.top, sizeof(layoutBounds.top));
632 cursor += sizeof(layoutBounds.top);
Adam Lesinski21efb682016-09-14 17:35:43 -0700633
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700634 memcpy(cursor, &layoutBounds.right, sizeof(layoutBounds.right));
635 cursor += sizeof(layoutBounds.right);
Adam Lesinski21efb682016-09-14 17:35:43 -0700636
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700637 memcpy(cursor, &layoutBounds.bottom, sizeof(layoutBounds.bottom));
638 cursor += sizeof(layoutBounds.bottom);
Adam Lesinski21efb682016-09-14 17:35:43 -0700639
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700640 *outLen = chunkLen;
641 return buffer;
Adam Lesinski21efb682016-09-14 17:35:43 -0700642}
643
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700644std::unique_ptr<uint8_t[]> NinePatch::serializeRoundedRectOutline(
645 size_t* outLen) const {
646 size_t chunkLen = sizeof(uint32_t) * 6;
647 auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[chunkLen]);
648 uint8_t* cursor = buffer.get();
Adam Lesinski21efb682016-09-14 17:35:43 -0700649
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700650 memcpy(cursor, &outline.left, sizeof(outline.left));
651 cursor += sizeof(outline.left);
Adam Lesinski21efb682016-09-14 17:35:43 -0700652
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700653 memcpy(cursor, &outline.top, sizeof(outline.top));
654 cursor += sizeof(outline.top);
Adam Lesinski21efb682016-09-14 17:35:43 -0700655
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700656 memcpy(cursor, &outline.right, sizeof(outline.right));
657 cursor += sizeof(outline.right);
Adam Lesinski21efb682016-09-14 17:35:43 -0700658
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700659 memcpy(cursor, &outline.bottom, sizeof(outline.bottom));
660 cursor += sizeof(outline.bottom);
Adam Lesinski21efb682016-09-14 17:35:43 -0700661
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700662 *((float*)cursor) = outlineRadius;
663 cursor += sizeof(outlineRadius);
Adam Lesinski21efb682016-09-14 17:35:43 -0700664
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700665 *((uint32_t*)cursor) = outlineAlpha;
Adam Lesinski21efb682016-09-14 17:35:43 -0700666
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700667 *outLen = chunkLen;
668 return buffer;
Adam Lesinski21efb682016-09-14 17:35:43 -0700669}
670
671::std::ostream& operator<<(::std::ostream& out, const Range& range) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700672 return out << "[" << range.start << ", " << range.end << ")";
Adam Lesinski21efb682016-09-14 17:35:43 -0700673}
674
675::std::ostream& operator<<(::std::ostream& out, const Bounds& bounds) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700676 return out << "l=" << bounds.left << " t=" << bounds.top
677 << " r=" << bounds.right << " b=" << bounds.bottom;
Adam Lesinski21efb682016-09-14 17:35:43 -0700678}
679
680::std::ostream& operator<<(::std::ostream& out, const NinePatch& ninePatch) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700681 return out << "horizontalStretch:"
682 << util::joiner(ninePatch.horizontalStretchRegions, " ")
683 << " verticalStretch:"
684 << util::joiner(ninePatch.verticalStretchRegions, " ")
685 << " padding: " << ninePatch.padding
686 << ", bounds: " << ninePatch.layoutBounds
687 << ", outline: " << ninePatch.outline
688 << " rad=" << ninePatch.outlineRadius
689 << " alpha=" << ninePatch.outlineAlpha;
Adam Lesinski21efb682016-09-14 17:35:43 -0700690}
691
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700692} // namespace aapt