blob: 0fc1c5d83db57a727ffca3f72e62a38ab1e014c1 [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;
31constexpr static const uint32_t kColorOpaqueRed = 0xffff0000u;
32
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 {
49public:
50 virtual ~ColorValidator() = default;
51
52 /**
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;
57
58 /**
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;
67 }
68 return isNeutralColor(color);
69 }
70};
71
72// Walks an ImageLine and records Ranges of primary and secondary colors.
73// The primary color is black and is used to denote a padding or stretching range,
74// depending on which border we're iterating over.
75// The secondary color is red and is used to denote optical bounds.
76//
77// An ImageLine is a templated-interface that would look something like this if it
78// were polymorphic:
79//
80// class ImageLine {
81// public:
82// virtual int32_t getLength() const = 0;
83// virtual uint32_t getColor(int32_t idx) const = 0;
84// };
85//
86template <typename ImageLine>
87static bool fillRanges(const ImageLine* imageLine,
88 const ColorValidator* colorValidator,
89 std::vector<Range>* primaryRanges,
90 std::vector<Range>* secondaryRanges,
91 std::string* err) {
92 const int32_t length = imageLine->getLength();
93
94 uint32_t lastColor = 0xffffffffu;
95 for (int32_t idx = 1; idx < length - 1; idx++) {
96 const uint32_t color = imageLine->getColor(idx);
97 if (!colorValidator->isValidColor(color)) {
98 *err = "found an invalid color";
99 return false;
100 }
101
102 if (color != lastColor) {
103 // We are ending a range. Which range?
104 // note: encode the x offset without the final 1 pixel border.
105 if (lastColor == kPrimaryColor) {
106 primaryRanges->back().end = idx - 1;
107 } else if (lastColor == kSecondaryColor) {
108 secondaryRanges->back().end = idx - 1;
109 }
110
111 // We are starting a range. Which range?
112 // note: encode the x offset without the final 1 pixel border.
113 if (color == kPrimaryColor) {
114 primaryRanges->push_back(Range(idx - 1, length - 2));
115 } else if (color == kSecondaryColor) {
116 secondaryRanges->push_back(Range(idx - 1, length - 2));
117 }
118 lastColor = color;
119 }
120 }
121 return true;
122}
123
124/**
125 * Iterates over a row in an image. Implements the templated ImageLine interface.
126 */
127class HorizontalImageLine {
128public:
129 explicit HorizontalImageLine(uint8_t** rows, int32_t xOffset, int32_t yOffset,
130 int32_t length) :
131 mRows(rows), mXOffset(xOffset), mYOffset(yOffset), mLength(length) {
132 }
133
134 inline int32_t getLength() const {
135 return mLength;
136 }
137
138 inline uint32_t getColor(int32_t idx) const {
139 return NinePatch::packRGBA(mRows[mYOffset] + (idx + mXOffset) * 4);
140 }
141
142private:
143 uint8_t** mRows;
144 int32_t mXOffset, mYOffset, mLength;
145
146 DISALLOW_COPY_AND_ASSIGN(HorizontalImageLine);
147};
148
149/**
150 * Iterates over a column in an image. Implements the templated ImageLine interface.
151 */
152class VerticalImageLine {
153public:
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) {
157 }
158
159 inline int32_t getLength() const {
160 return mLength;
161 }
162
163 inline uint32_t getColor(int32_t idx) const {
164 return NinePatch::packRGBA(mRows[mYOffset + idx] + (mXOffset * 4));
165 }
166
167private:
168 uint8_t** mRows;
169 int32_t mXOffset, mYOffset, mLength;
170
171 DISALLOW_COPY_AND_ASSIGN(VerticalImageLine);
172};
173
174class DiagonalImageLine {
175public:
176 explicit DiagonalImageLine(uint8_t** rows, int32_t xOffset, int32_t yOffset,
177 int32_t xStep, int32_t yStep, int32_t length) :
178 mRows(rows), mXOffset(xOffset), mYOffset(yOffset), mXStep(xStep), mYStep(yStep),
179 mLength(length) {
180 }
181
182 inline int32_t getLength() const {
183 return mLength;
184 }
185
186 inline uint32_t getColor(int32_t idx) const {
187 return NinePatch::packRGBA(
188 mRows[mYOffset + (idx * mYStep)] + ((idx + mXOffset) * mXStep) * 4);
189 }
190
191private:
192 uint8_t** mRows;
193 int32_t mXOffset, mYOffset, mXStep, mYStep, mLength;
194
195 DISALLOW_COPY_AND_ASSIGN(DiagonalImageLine);
196};
197
198class TransparentNeutralColorValidator : public ColorValidator {
199public:
200 bool isNeutralColor(uint32_t color) const override {
201 return getAlpha(color) == 0;
202 }
203};
204
205class WhiteNeutralColorValidator : public ColorValidator {
206public:
207 bool isNeutralColor(uint32_t color) const override {
208 return color == kColorOpaqueWhite;
209 }
210};
211
212inline static uint32_t getAlpha(uint32_t color) {
213 return (color & 0xff000000u) >> 24;
214}
215
216static bool populateBounds(const std::vector<Range>& padding,
217 const std::vector<Range>& layoutBounds,
218 const std::vector<Range>& stretchRegions,
219 const int32_t length,
220 int32_t* paddingStart, int32_t* paddingEnd,
221 int32_t* layoutStart, int32_t* layoutEnd,
222 const StringPiece& edgeName,
223 std::string* err) {
224 if (padding.size() > 1) {
225 std::stringstream errStream;
226 errStream << "too many padding sections on " << edgeName << " border";
227 *err = errStream.str();
228 return false;
229 }
230
231 *paddingStart = 0;
232 *paddingEnd = 0;
233 if (!padding.empty()) {
234 const Range& range = padding.front();
235 *paddingStart = range.start;
236 *paddingEnd = length - range.end;
237 } else if (!stretchRegions.empty()) {
238 // No padding was defined. Compute the padding from the first and last
239 // stretch regions.
240 *paddingStart = stretchRegions.front().start;
241 *paddingEnd = length - stretchRegions.back().end;
242 }
243
244 if (layoutBounds.size() > 2) {
245 std::stringstream errStream;
246 errStream << "too many layout bounds sections on " << edgeName << " border";
247 *err = errStream.str();
248 return false;
249 }
250
251 *layoutStart = 0;
252 *layoutEnd = 0;
253 if (layoutBounds.size() >= 1) {
254 const Range& range = layoutBounds.front();
255 // If there is only one layout bound segment, it might not start at 0, but then it should
256 // end at length.
257 if (range.start != 0 && range.end != length) {
258 std::stringstream errStream;
259 errStream << "layout bounds on " << edgeName << " border must start at edge";
260 *err = errStream.str();
261 return false;
262 }
263 *layoutStart = range.end;
264
265 if (layoutBounds.size() >= 2) {
266 const Range& range = layoutBounds.back();
267 if (range.end != length) {
268 std::stringstream errStream;
269 errStream << "layout bounds on " << edgeName << " border must start at edge";
270 *err = errStream.str();
271 return false;
272 }
273 *layoutEnd = length - range.start;
274 }
275 }
276 return true;
277}
278
279static int32_t calculateSegmentCount(const std::vector<Range>& stretchRegions, int32_t length) {
280 if (stretchRegions.size() == 0) {
281 return 0;
282 }
283
284 const bool startIsFixed = stretchRegions.front().start != 0;
285 const bool endIsFixed = stretchRegions.back().end != length;
286 int32_t modifier = 0;
287 if (startIsFixed && endIsFixed) {
288 modifier = 1;
289 } else if (!startIsFixed && !endIsFixed) {
290 modifier = -1;
291 }
292 return static_cast<int32_t>(stretchRegions.size()) * 2 + modifier;
293}
294
295static uint32_t getRegionColor(uint8_t** rows, const Bounds& region) {
296 // Sample the first pixel to compare against.
297 const uint32_t expectedColor = NinePatch::packRGBA(rows[region.top] + region.left * 4);
298 for (int32_t y = region.top; y < region.bottom; y++) {
299 const uint8_t* row = rows[y];
300 for (int32_t x = region.left; x < region.right; x++) {
301 const uint32_t color = NinePatch::packRGBA(row + x * 4);
302 if (getAlpha(color) == 0) {
303 // The color is transparent.
304 // If the expectedColor is not transparent, NO_COLOR.
305 if (getAlpha(expectedColor) != 0) {
306 return android::Res_png_9patch::NO_COLOR;
307 }
308 } else if (color != expectedColor) {
309 return android::Res_png_9patch::NO_COLOR;
310 }
311 }
312 }
313
314 if (getAlpha(expectedColor) == 0) {
315 return android::Res_png_9patch::TRANSPARENT_COLOR;
316 }
317 return expectedColor;
318}
319
320// Fills outColors with each 9-patch section's colour. If the whole section is transparent,
321// it gets the special TRANSPARENT colour. If the whole section is the same colour, it is assigned
322// that colour. Otherwise it gets the special NO_COLOR colour.
323//
324// Note that the rows contain the 9-patch 1px border, and the indices in the stretch regions are
325// already offset to exclude the border. This means that each time the rows are accessed,
326// the indices must be offset by 1.
327//
328// width and height also include the 9-patch 1px border.
329static void calculateRegionColors(uint8_t** rows,
330 const std::vector<Range>& horizontalStretchRegions,
331 const std::vector<Range>& verticalStretchRegions,
332 const int32_t width, const int32_t height,
333 std::vector<uint32_t>* outColors) {
334 int32_t nextTop = 0;
335 Bounds bounds;
336 auto rowIter = verticalStretchRegions.begin();
337 while (nextTop != height) {
338 if (rowIter != verticalStretchRegions.end()) {
339 if (nextTop != rowIter->start) {
340 // This is a fixed segment.
341 // Offset the bounds by 1 to accommodate the border.
342 bounds.top = nextTop + 1;
343 bounds.bottom = rowIter->start + 1;
344 nextTop = rowIter->start;
345 } else {
346 // This is a stretchy segment.
347 // Offset the bounds by 1 to accommodate the border.
348 bounds.top = rowIter->start + 1;
349 bounds.bottom = rowIter->end + 1;
350 nextTop = rowIter->end;
351 ++rowIter;
352 }
353 } else {
354 // This is the end, fixed section.
355 // Offset the bounds by 1 to accommodate the border.
356 bounds.top = nextTop + 1;
357 bounds.bottom = height + 1;
358 nextTop = height;
359 }
360
361 int32_t nextLeft = 0;
362 auto colIter = horizontalStretchRegions.begin();
363 while (nextLeft != width) {
364 if (colIter != horizontalStretchRegions.end()) {
365 if (nextLeft != colIter->start) {
366 // This is a fixed segment.
367 // Offset the bounds by 1 to accommodate the border.
368 bounds.left = nextLeft + 1;
369 bounds.right = colIter->start + 1;
370 nextLeft = colIter->start;
371 } else {
372 // This is a stretchy segment.
373 // Offset the bounds by 1 to accommodate the border.
374 bounds.left = colIter->start + 1;
375 bounds.right = colIter->end + 1;
376 nextLeft = colIter->end;
377 ++colIter;
378 }
379 } else {
380 // This is the end, fixed section.
381 // Offset the bounds by 1 to accommodate the border.
382 bounds.left = nextLeft + 1;
383 bounds.right = width + 1;
384 nextLeft = width;
385 }
386 outColors->push_back(getRegionColor(rows, bounds));
387 }
388 }
389}
390
391// Calculates the insets of a row/column of pixels based on where the largest alpha value begins
392// (on both sides).
393template <typename ImageLine>
394static void findOutlineInsets(const ImageLine* imageLine, int32_t* outStart, int32_t* outEnd) {
395 *outStart = 0;
396 *outEnd = 0;
397
398 const int32_t length = imageLine->getLength();
399 if (length < 3) {
400 return;
401 }
402
403 // If the length is odd, we want both sides to process the center pixel,
404 // so we use two different midpoints (to account for < and <= in the different loops).
405 const int32_t mid2 = length / 2;
406 const int32_t mid1 = mid2 + (length % 2);
407
408 uint32_t maxAlpha = 0;
409 for (int32_t i = 0; i < mid1 && maxAlpha != 0xff; i++) {
410 uint32_t alpha = getAlpha(imageLine->getColor(i));
411 if (alpha > maxAlpha) {
412 maxAlpha = alpha;
413 *outStart = i;
414 }
415 }
416
417 maxAlpha = 0;
418 for (int32_t i = length - 1; i >= mid2 && maxAlpha != 0xff; i--) {
419 uint32_t alpha = getAlpha(imageLine->getColor(i));
420 if (alpha > maxAlpha) {
421 maxAlpha = alpha;
422 *outEnd = length - (i + 1);
423 }
424 }
425 return;
426}
427
428template <typename ImageLine>
429static uint32_t findMaxAlpha(const ImageLine* imageLine) {
430 const int32_t length = imageLine->getLength();
431 uint32_t maxAlpha = 0;
432 for (int32_t idx = 0; idx < length && maxAlpha != 0xff; idx++) {
433 uint32_t alpha = getAlpha(imageLine->getColor(idx));
434 if (alpha > maxAlpha) {
435 maxAlpha = alpha;
436 }
437 }
438 return maxAlpha;
439}
440
441// Pack the pixels in as 0xAARRGGBB (as 9-patch expects it).
442uint32_t NinePatch::packRGBA(const uint8_t* pixel) {
443 return (pixel[3] << 24) | (pixel[0] << 16) | (pixel[1] << 8) | pixel[2];
444}
445
446std::unique_ptr<NinePatch> NinePatch::create(uint8_t** rows,
447 const int32_t width, const int32_t height,
448 std::string* err) {
449 if (width < 3 || height < 3) {
450 *err = "image must be at least 3x3 (1x1 image with 1 pixel border)";
451 return {};
452 }
453
454 std::vector<Range> horizontalPadding;
455 std::vector<Range> horizontalOpticalBounds;
456 std::vector<Range> verticalPadding;
457 std::vector<Range> verticalOpticalBounds;
458 std::vector<Range> unexpectedRanges;
459 std::unique_ptr<ColorValidator> colorValidator;
460
461 if (rows[0][3] == 0) {
462 colorValidator = util::make_unique<TransparentNeutralColorValidator>();
463 } else if (packRGBA(rows[0]) == kColorOpaqueWhite) {
464 colorValidator = util::make_unique<WhiteNeutralColorValidator>();
465 } else {
466 *err = "top-left corner pixel must be either opaque white or transparent";
467 return {};
468 }
469
470 // Private constructor, can't use make_unique.
471 auto ninePatch = std::unique_ptr<NinePatch>(new NinePatch());
472
473 HorizontalImageLine topRow(rows, 0, 0, width);
474 if (!fillRanges(&topRow, colorValidator.get(), &ninePatch->horizontalStretchRegions,
475 &unexpectedRanges, err)) {
476 return {};
477 }
478
479 if (!unexpectedRanges.empty()) {
480 const Range& range = unexpectedRanges[0];
481 std::stringstream errStream;
482 errStream << "found unexpected optical bounds (red pixel) on top border "
483 << "at x=" << range.start + 1;
484 *err = errStream.str();
485 return {};
486 }
487
488 VerticalImageLine leftCol(rows, 0, 0, height);
489 if (!fillRanges(&leftCol, colorValidator.get(), &ninePatch->verticalStretchRegions,
490 &unexpectedRanges, err)) {
491 return {};
492 }
493
494 if (!unexpectedRanges.empty()) {
495 const Range& range = unexpectedRanges[0];
496 std::stringstream errStream;
497 errStream << "found unexpected optical bounds (red pixel) on left border "
498 << "at y=" << range.start + 1;
499 return {};
500 }
501
502 HorizontalImageLine bottomRow(rows, 0, height - 1, width);
503 if (!fillRanges(&bottomRow, colorValidator.get(), &horizontalPadding,
504 &horizontalOpticalBounds, err)) {
505 return {};
506 }
507
508 if (!populateBounds(horizontalPadding, horizontalOpticalBounds,
509 ninePatch->horizontalStretchRegions, width - 2,
510 &ninePatch->padding.left, &ninePatch->padding.right,
511 &ninePatch->layoutBounds.left, &ninePatch->layoutBounds.right,
512 "bottom", err)) {
513 return {};
514 }
515
516 VerticalImageLine rightCol(rows, width - 1, 0, height);
517 if (!fillRanges(&rightCol, colorValidator.get(), &verticalPadding,
518 &verticalOpticalBounds, err)) {
519 return {};
520 }
521
522 if (!populateBounds(verticalPadding, verticalOpticalBounds,
523 ninePatch->verticalStretchRegions, height - 2,
524 &ninePatch->padding.top, &ninePatch->padding.bottom,
525 &ninePatch->layoutBounds.top, &ninePatch->layoutBounds.bottom,
526 "right", err)) {
527 return {};
528 }
529
530 // Fill the region colors of the 9-patch.
531 const int32_t numRows = calculateSegmentCount(ninePatch->horizontalStretchRegions, width - 2);
532 const int32_t numCols = calculateSegmentCount(ninePatch->verticalStretchRegions, height - 2);
533 if ((int64_t) numRows * (int64_t) numCols > 0x7f) {
534 *err = "too many regions in 9-patch";
535 return {};
536 }
537
538 ninePatch->regionColors.reserve(numRows * numCols);
539 calculateRegionColors(rows, ninePatch->horizontalStretchRegions,
540 ninePatch->verticalStretchRegions,
541 width - 2, height - 2,
542 &ninePatch->regionColors);
543
544 // Compute the outline based on opacity.
545
546 // Find left and right extent of 9-patch content on center row.
547 HorizontalImageLine midRow(rows, 1, height / 2, width - 2);
548 findOutlineInsets(&midRow, &ninePatch->outline.left, &ninePatch->outline.right);
549
550 // Find top and bottom extent of 9-patch content on center column.
551 VerticalImageLine midCol(rows, width / 2, 1, height - 2);
552 findOutlineInsets(&midCol, &ninePatch->outline.top, &ninePatch->outline.bottom);
553
554 const int32_t outlineWidth = (width - 2) - ninePatch->outline.left - ninePatch->outline.right;
555 const int32_t outlineHeight = (height - 2) - ninePatch->outline.top - ninePatch->outline.bottom;
556
557 // Find the largest alpha value within the outline area.
558 HorizontalImageLine outlineMidRow(rows,
559 1 + ninePatch->outline.left,
560 1 + ninePatch->outline.top + (outlineHeight / 2),
561 outlineWidth);
562 VerticalImageLine outlineMidCol(rows,
563 1 + ninePatch->outline.left + (outlineWidth / 2),
564 1 + ninePatch->outline.top,
565 outlineHeight);
566 ninePatch->outlineAlpha = std::max(findMaxAlpha(&outlineMidRow), findMaxAlpha(&outlineMidCol));
567
568 // Assuming the image is a round rect, compute the radius by marching
569 // diagonally from the top left corner towards the center.
570 DiagonalImageLine diagonal(rows, 1 + ninePatch->outline.left, 1 + ninePatch->outline.top,
571 1, 1, std::min(outlineWidth, outlineHeight));
572 int32_t topLeft, bottomRight;
573 findOutlineInsets(&diagonal, &topLeft, &bottomRight);
574
575 /* Determine source radius based upon inset:
576 * sqrt(r^2 + r^2) = sqrt(i^2 + i^2) + r
577 * sqrt(2) * r = sqrt(2) * i + r
578 * (sqrt(2) - 1) * r = sqrt(2) * i
579 * r = sqrt(2) / (sqrt(2) - 1) * i
580 */
581 ninePatch->outlineRadius = 3.4142f * topLeft;
582 return ninePatch;
583}
584
585std::unique_ptr<uint8_t[]> NinePatch::serializeBase(size_t* outLen) const {
586 android::Res_png_9patch data;
587 data.numXDivs = static_cast<uint8_t>(horizontalStretchRegions.size()) * 2;
588 data.numYDivs = static_cast<uint8_t>(verticalStretchRegions.size()) * 2;
589 data.numColors = static_cast<uint8_t>(regionColors.size());
590 data.paddingLeft = padding.left;
591 data.paddingRight = padding.right;
592 data.paddingTop = padding.top;
593 data.paddingBottom = padding.bottom;
594
595 auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[data.serializedSize()]);
596 android::Res_png_9patch::serialize(data,
597 (const int32_t*) horizontalStretchRegions.data(),
598 (const int32_t*) verticalStretchRegions.data(),
599 regionColors.data(),
600 buffer.get());
Adam Lesinskiedba9412016-10-04 17:33:04 -0700601 // Convert to file endianness.
602 reinterpret_cast<android::Res_png_9patch*>(buffer.get())->deviceToFile();
603
Adam Lesinski21efb682016-09-14 17:35:43 -0700604 *outLen = data.serializedSize();
605 return buffer;
606}
607
608std::unique_ptr<uint8_t[]> NinePatch::serializeLayoutBounds(size_t* outLen) const {
609 size_t chunkLen = sizeof(uint32_t) * 4;
610 auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[chunkLen]);
611 uint8_t* cursor = buffer.get();
612
613 memcpy(cursor, &layoutBounds.left, sizeof(layoutBounds.left));
614 cursor += sizeof(layoutBounds.left);
615
616 memcpy(cursor, &layoutBounds.top, sizeof(layoutBounds.top));
617 cursor += sizeof(layoutBounds.top);
618
619 memcpy(cursor, &layoutBounds.right, sizeof(layoutBounds.right));
620 cursor += sizeof(layoutBounds.right);
621
622 memcpy(cursor, &layoutBounds.bottom, sizeof(layoutBounds.bottom));
623 cursor += sizeof(layoutBounds.bottom);
624
625 *outLen = chunkLen;
626 return buffer;
627}
628
629std::unique_ptr<uint8_t[]> NinePatch::serializeRoundedRectOutline(size_t* outLen) const {
630 size_t chunkLen = sizeof(uint32_t) * 6;
631 auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[chunkLen]);
632 uint8_t* cursor = buffer.get();
633
634 memcpy(cursor, &outline.left, sizeof(outline.left));
635 cursor += sizeof(outline.left);
636
637 memcpy(cursor, &outline.top, sizeof(outline.top));
638 cursor += sizeof(outline.top);
639
640 memcpy(cursor, &outline.right, sizeof(outline.right));
641 cursor += sizeof(outline.right);
642
643 memcpy(cursor, &outline.bottom, sizeof(outline.bottom));
644 cursor += sizeof(outline.bottom);
645
646 *((float*) cursor) = outlineRadius;
647 cursor += sizeof(outlineRadius);
648
649 *((uint32_t*) cursor) = outlineAlpha;
650
651 *outLen = chunkLen;
652 return buffer;
653}
654
655::std::ostream& operator<<(::std::ostream& out, const Range& range) {
656 return out << "[" << range.start << ", " << range.end << ")";
657}
658
659::std::ostream& operator<<(::std::ostream& out, const Bounds& bounds) {
660 return out << "l=" << bounds.left
661 << " t=" << bounds.top
662 << " r=" << bounds.right
663 << " b=" << bounds.bottom;
664}
665
666::std::ostream& operator<<(::std::ostream& out, const NinePatch& ninePatch) {
Adam Lesinskiedba9412016-10-04 17:33:04 -0700667 return out << "horizontalStretch:" << util::joiner(ninePatch.horizontalStretchRegions, " ")
668 << " verticalStretch:" << util::joiner(ninePatch.verticalStretchRegions, " ")
669 << " padding: " << ninePatch.padding
Adam Lesinski21efb682016-09-14 17:35:43 -0700670 << ", bounds: " << ninePatch.layoutBounds
671 << ", outline: " << ninePatch.outline
672 << " rad=" << ninePatch.outlineRadius
673 << " alpha=" << ninePatch.outlineAlpha;
674}
675
676} // namespace aapt