1 /* 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 19 #include <sstream> 20 #include <string> 21 #include <vector> 22 23 #include "androidfw/ResourceTypes.h" 24 #include "androidfw/StringPiece.h" 25 26 #include "util/Util.h" 27 28 using android::StringPiece; 29 30 namespace aapt { 31 32 // Colors in the format 0xAARRGGBB (the way 9-patch expects it). 33 constexpr static const uint32_t kColorOpaqueWhite = 0xffffffffu; 34 constexpr static const uint32_t kColorOpaqueBlack = 0xff000000u; 35 constexpr static const uint32_t kColorOpaqueRed = 0xffff0000u; 36 37 constexpr static const uint32_t kPrimaryColor = kColorOpaqueBlack; 38 constexpr static const uint32_t kSecondaryColor = kColorOpaqueRed; 39 40 /** 41 * Returns the alpha value encoded in the 0xAARRGBB encoded pixel. 42 */ 43 static uint32_t get_alpha(uint32_t color); 44 45 /** 46 * Determines whether a color on an ImageLine is valid. 47 * A 9patch image may use a transparent color as neutral, 48 * or a fully opaque white color as neutral, based on the 49 * pixel color at (0,0) of the image. One or the other is fine, 50 * but we need to ensure consistency throughout the image. 51 */ 52 class ColorValidator { 53 public: 54 virtual ~ColorValidator() = default; 55 56 /** 57 * Returns true if the color specified is a neutral color 58 * (no padding, stretching, or optical bounds). 59 */ 60 virtual bool IsNeutralColor(uint32_t color) const = 0; 61 62 /** 63 * Returns true if the color is either a neutral color 64 * or one denoting padding, stretching, or optical bounds. 65 */ 66 bool IsValidColor(uint32_t color) const { 67 switch (color) { 68 case kPrimaryColor: 69 case kSecondaryColor: 70 return true; 71 } 72 return IsNeutralColor(color); 73 } 74 }; 75 76 // Walks an ImageLine and records Ranges of primary and secondary colors. 77 // The primary color is black and is used to denote a padding or stretching 78 // range, 79 // depending on which border we're iterating over. 80 // The secondary color is red and is used to denote optical bounds. 81 // 82 // An ImageLine is a templated-interface that would look something like this if 83 // it 84 // were polymorphic: 85 // 86 // class ImageLine { 87 // public: 88 // virtual int32_t GetLength() const = 0; 89 // virtual uint32_t GetColor(int32_t idx) const = 0; 90 // }; 91 // 92 template <typename ImageLine> 93 static bool FillRanges(const ImageLine* image_line, 94 const ColorValidator* color_validator, 95 std::vector<Range>* primary_ranges, 96 std::vector<Range>* secondary_ranges, 97 std::string* out_err) { 98 const int32_t length = image_line->GetLength(); 99 100 uint32_t last_color = 0xffffffffu; 101 for (int32_t idx = 1; idx < length - 1; idx++) { 102 const uint32_t color = image_line->GetColor(idx); 103 if (!color_validator->IsValidColor(color)) { 104 *out_err = "found an invalid color"; 105 return false; 106 } 107 108 if (color != last_color) { 109 // We are ending a range. Which range? 110 // note: encode the x offset without the final 1 pixel border. 111 if (last_color == kPrimaryColor) { 112 primary_ranges->back().end = idx - 1; 113 } else if (last_color == kSecondaryColor) { 114 secondary_ranges->back().end = idx - 1; 115 } 116 117 // We are starting a range. Which range? 118 // note: encode the x offset without the final 1 pixel border. 119 if (color == kPrimaryColor) { 120 primary_ranges->push_back(Range(idx - 1, length - 2)); 121 } else if (color == kSecondaryColor) { 122 secondary_ranges->push_back(Range(idx - 1, length - 2)); 123 } 124 last_color = color; 125 } 126 } 127 return true; 128 } 129 130 /** 131 * Iterates over a row in an image. Implements the templated ImageLine 132 * interface. 133 */ 134 class HorizontalImageLine { 135 public: 136 explicit HorizontalImageLine(uint8_t** rows, int32_t xoffset, int32_t yoffset, 137 int32_t length) 138 : rows_(rows), xoffset_(xoffset), yoffset_(yoffset), length_(length) {} 139 140 inline int32_t GetLength() const { return length_; } 141 142 inline uint32_t GetColor(int32_t idx) const { 143 return NinePatch::PackRGBA(rows_[yoffset_] + (idx + xoffset_) * 4); 144 } 145 146 private: 147 uint8_t** rows_; 148 int32_t xoffset_, yoffset_, length_; 149 150 DISALLOW_COPY_AND_ASSIGN(HorizontalImageLine); 151 }; 152 153 /** 154 * Iterates over a column in an image. Implements the templated ImageLine 155 * interface. 156 */ 157 class VerticalImageLine { 158 public: 159 explicit VerticalImageLine(uint8_t** rows, int32_t xoffset, int32_t yoffset, 160 int32_t length) 161 : rows_(rows), xoffset_(xoffset), yoffset_(yoffset), length_(length) {} 162 163 inline int32_t GetLength() const { return length_; } 164 165 inline uint32_t GetColor(int32_t idx) const { 166 return NinePatch::PackRGBA(rows_[yoffset_ + idx] + (xoffset_ * 4)); 167 } 168 169 private: 170 uint8_t** rows_; 171 int32_t xoffset_, yoffset_, length_; 172 173 DISALLOW_COPY_AND_ASSIGN(VerticalImageLine); 174 }; 175 176 class DiagonalImageLine { 177 public: 178 explicit DiagonalImageLine(uint8_t** rows, int32_t xoffset, int32_t yoffset, 179 int32_t xstep, int32_t ystep, int32_t length) 180 : rows_(rows), 181 xoffset_(xoffset), 182 yoffset_(yoffset), 183 xstep_(xstep), 184 ystep_(ystep), 185 length_(length) {} 186 187 inline int32_t GetLength() const { return length_; } 188 189 inline uint32_t GetColor(int32_t idx) const { 190 return NinePatch::PackRGBA(rows_[yoffset_ + (idx * ystep_)] + 191 ((idx + xoffset_) * xstep_) * 4); 192 } 193 194 private: 195 uint8_t** rows_; 196 int32_t xoffset_, yoffset_, xstep_, ystep_, length_; 197 198 DISALLOW_COPY_AND_ASSIGN(DiagonalImageLine); 199 }; 200 201 class TransparentNeutralColorValidator : public ColorValidator { 202 public: 203 bool IsNeutralColor(uint32_t color) const override { 204 return get_alpha(color) == 0; 205 } 206 }; 207 208 class WhiteNeutralColorValidator : public ColorValidator { 209 public: 210 bool IsNeutralColor(uint32_t color) const override { 211 return color == kColorOpaqueWhite; 212 } 213 }; 214 215 inline static uint32_t get_alpha(uint32_t color) { 216 return (color & 0xff000000u) >> 24; 217 } 218 219 static bool PopulateBounds(const std::vector<Range>& padding, 220 const std::vector<Range>& layout_bounds, 221 const std::vector<Range>& stretch_regions, 222 const int32_t length, int32_t* padding_start, 223 int32_t* padding_end, int32_t* layout_start, 224 int32_t* layout_end, const StringPiece& edge_name, 225 std::string* out_err) { 226 if (padding.size() > 1) { 227 std::stringstream err_stream; 228 err_stream << "too many padding sections on " << edge_name << " border"; 229 *out_err = err_stream.str(); 230 return false; 231 } 232 233 *padding_start = 0; 234 *padding_end = 0; 235 if (!padding.empty()) { 236 const Range& range = padding.front(); 237 *padding_start = range.start; 238 *padding_end = length - range.end; 239 } else if (!stretch_regions.empty()) { 240 // No padding was defined. Compute the padding from the first and last 241 // stretch regions. 242 *padding_start = stretch_regions.front().start; 243 *padding_end = length - stretch_regions.back().end; 244 } 245 246 if (layout_bounds.size() > 2) { 247 std::stringstream err_stream; 248 err_stream << "too many layout bounds sections on " << edge_name 249 << " border"; 250 *out_err = err_stream.str(); 251 return false; 252 } 253 254 *layout_start = 0; 255 *layout_end = 0; 256 if (layout_bounds.size() >= 1) { 257 const Range& range = layout_bounds.front(); 258 // If there is only one layout bound segment, it might not start at 0, but 259 // then it should 260 // end at length. 261 if (range.start != 0 && range.end != length) { 262 std::stringstream err_stream; 263 err_stream << "layout bounds on " << edge_name 264 << " border must start at edge"; 265 *out_err = err_stream.str(); 266 return false; 267 } 268 *layout_start = range.end; 269 270 if (layout_bounds.size() >= 2) { 271 const Range& range = layout_bounds.back(); 272 if (range.end != length) { 273 std::stringstream err_stream; 274 err_stream << "layout bounds on " << edge_name 275 << " border must start at edge"; 276 *out_err = err_stream.str(); 277 return false; 278 } 279 *layout_end = length - range.start; 280 } 281 } 282 return true; 283 } 284 285 static int32_t CalculateSegmentCount(const std::vector<Range>& stretch_regions, 286 int32_t length) { 287 if (stretch_regions.size() == 0) { 288 return 0; 289 } 290 291 const bool start_is_fixed = stretch_regions.front().start != 0; 292 const bool end_is_fixed = stretch_regions.back().end != length; 293 int32_t modifier = 0; 294 if (start_is_fixed && end_is_fixed) { 295 modifier = 1; 296 } else if (!start_is_fixed && !end_is_fixed) { 297 modifier = -1; 298 } 299 return static_cast<int32_t>(stretch_regions.size()) * 2 + modifier; 300 } 301 302 static uint32_t GetRegionColor(uint8_t** rows, const Bounds& region) { 303 // Sample the first pixel to compare against. 304 const uint32_t expected_color = 305 NinePatch::PackRGBA(rows[region.top] + region.left * 4); 306 for (int32_t y = region.top; y < region.bottom; y++) { 307 const uint8_t* row = rows[y]; 308 for (int32_t x = region.left; x < region.right; x++) { 309 const uint32_t color = NinePatch::PackRGBA(row + x * 4); 310 if (get_alpha(color) == 0) { 311 // The color is transparent. 312 // If the expectedColor is not transparent, NO_COLOR. 313 if (get_alpha(expected_color) != 0) { 314 return android::Res_png_9patch::NO_COLOR; 315 } 316 } else if (color != expected_color) { 317 return android::Res_png_9patch::NO_COLOR; 318 } 319 } 320 } 321 322 if (get_alpha(expected_color) == 0) { 323 return android::Res_png_9patch::TRANSPARENT_COLOR; 324 } 325 return expected_color; 326 } 327 328 // Fills out_colors with each 9-patch section's color. If the whole section is 329 // transparent, 330 // it gets the special TRANSPARENT color. If the whole section is the same 331 // color, it is assigned 332 // that color. Otherwise it gets the special NO_COLOR color. 333 // 334 // Note that the rows contain the 9-patch 1px border, and the indices in the 335 // stretch regions are 336 // already offset to exclude the border. This means that each time the rows are 337 // accessed, 338 // the indices must be offset by 1. 339 // 340 // width and height also include the 9-patch 1px border. 341 static void CalculateRegionColors( 342 uint8_t** rows, const std::vector<Range>& horizontal_stretch_regions, 343 const std::vector<Range>& vertical_stretch_regions, const int32_t width, 344 const int32_t height, std::vector<uint32_t>* out_colors) { 345 int32_t next_top = 0; 346 Bounds bounds; 347 auto row_iter = vertical_stretch_regions.begin(); 348 while (next_top != height) { 349 if (row_iter != vertical_stretch_regions.end()) { 350 if (next_top != row_iter->start) { 351 // This is a fixed segment. 352 // Offset the bounds by 1 to accommodate the border. 353 bounds.top = next_top + 1; 354 bounds.bottom = row_iter->start + 1; 355 next_top = row_iter->start; 356 } else { 357 // This is a stretchy segment. 358 // Offset the bounds by 1 to accommodate the border. 359 bounds.top = row_iter->start + 1; 360 bounds.bottom = row_iter->end + 1; 361 next_top = row_iter->end; 362 ++row_iter; 363 } 364 } else { 365 // This is the end, fixed section. 366 // Offset the bounds by 1 to accommodate the border. 367 bounds.top = next_top + 1; 368 bounds.bottom = height + 1; 369 next_top = height; 370 } 371 372 int32_t next_left = 0; 373 auto col_iter = horizontal_stretch_regions.begin(); 374 while (next_left != width) { 375 if (col_iter != horizontal_stretch_regions.end()) { 376 if (next_left != col_iter->start) { 377 // This is a fixed segment. 378 // Offset the bounds by 1 to accommodate the border. 379 bounds.left = next_left + 1; 380 bounds.right = col_iter->start + 1; 381 next_left = col_iter->start; 382 } else { 383 // This is a stretchy segment. 384 // Offset the bounds by 1 to accommodate the border. 385 bounds.left = col_iter->start + 1; 386 bounds.right = col_iter->end + 1; 387 next_left = col_iter->end; 388 ++col_iter; 389 } 390 } else { 391 // This is the end, fixed section. 392 // Offset the bounds by 1 to accommodate the border. 393 bounds.left = next_left + 1; 394 bounds.right = width + 1; 395 next_left = width; 396 } 397 out_colors->push_back(GetRegionColor(rows, bounds)); 398 } 399 } 400 } 401 402 // Calculates the insets of a row/column of pixels based on where the largest 403 // alpha value begins 404 // (on both sides). 405 template <typename ImageLine> 406 static void FindOutlineInsets(const ImageLine* image_line, int32_t* out_start, 407 int32_t* out_end) { 408 *out_start = 0; 409 *out_end = 0; 410 411 const int32_t length = image_line->GetLength(); 412 if (length < 3) { 413 return; 414 } 415 416 // If the length is odd, we want both sides to process the center pixel, 417 // so we use two different midpoints (to account for < and <= in the different 418 // loops). 419 const int32_t mid2 = length / 2; 420 const int32_t mid1 = mid2 + (length % 2); 421 422 uint32_t max_alpha = 0; 423 for (int32_t i = 0; i < mid1 && max_alpha != 0xff; i++) { 424 uint32_t alpha = get_alpha(image_line->GetColor(i)); 425 if (alpha > max_alpha) { 426 max_alpha = alpha; 427 *out_start = i; 428 } 429 } 430 431 max_alpha = 0; 432 for (int32_t i = length - 1; i >= mid2 && max_alpha != 0xff; i--) { 433 uint32_t alpha = get_alpha(image_line->GetColor(i)); 434 if (alpha > max_alpha) { 435 max_alpha = alpha; 436 *out_end = length - (i + 1); 437 } 438 } 439 return; 440 } 441 442 template <typename ImageLine> 443 static uint32_t FindMaxAlpha(const ImageLine* image_line) { 444 const int32_t length = image_line->GetLength(); 445 uint32_t max_alpha = 0; 446 for (int32_t idx = 0; idx < length && max_alpha != 0xff; idx++) { 447 uint32_t alpha = get_alpha(image_line->GetColor(idx)); 448 if (alpha > max_alpha) { 449 max_alpha = alpha; 450 } 451 } 452 return max_alpha; 453 } 454 455 // Pack the pixels in as 0xAARRGGBB (as 9-patch expects it). 456 uint32_t NinePatch::PackRGBA(const uint8_t* pixel) { 457 return (pixel[3] << 24) | (pixel[0] << 16) | (pixel[1] << 8) | pixel[2]; 458 } 459 460 std::unique_ptr<NinePatch> NinePatch::Create(uint8_t** rows, 461 const int32_t width, 462 const int32_t height, 463 std::string* out_err) { 464 if (width < 3 || height < 3) { 465 *out_err = "image must be at least 3x3 (1x1 image with 1 pixel border)"; 466 return {}; 467 } 468 469 std::vector<Range> horizontal_padding; 470 std::vector<Range> horizontal_layout_bounds; 471 std::vector<Range> vertical_padding; 472 std::vector<Range> vertical_layout_bounds; 473 std::vector<Range> unexpected_ranges; 474 std::unique_ptr<ColorValidator> color_validator; 475 476 if (rows[0][3] == 0) { 477 color_validator = util::make_unique<TransparentNeutralColorValidator>(); 478 } else if (PackRGBA(rows[0]) == kColorOpaqueWhite) { 479 color_validator = util::make_unique<WhiteNeutralColorValidator>(); 480 } else { 481 *out_err = 482 "top-left corner pixel must be either opaque white or transparent"; 483 return {}; 484 } 485 486 // Private constructor, can't use make_unique. 487 auto nine_patch = std::unique_ptr<NinePatch>(new NinePatch()); 488 489 HorizontalImageLine top_row(rows, 0, 0, width); 490 if (!FillRanges(&top_row, color_validator.get(), 491 &nine_patch->horizontal_stretch_regions, &unexpected_ranges, 492 out_err)) { 493 return {}; 494 } 495 496 if (!unexpected_ranges.empty()) { 497 const Range& range = unexpected_ranges[0]; 498 std::stringstream err_stream; 499 err_stream << "found unexpected optical bounds (red pixel) on top border " 500 << "at x=" << range.start + 1; 501 *out_err = err_stream.str(); 502 return {}; 503 } 504 505 VerticalImageLine left_col(rows, 0, 0, height); 506 if (!FillRanges(&left_col, color_validator.get(), 507 &nine_patch->vertical_stretch_regions, &unexpected_ranges, 508 out_err)) { 509 return {}; 510 } 511 512 if (!unexpected_ranges.empty()) { 513 const Range& range = unexpected_ranges[0]; 514 std::stringstream err_stream; 515 err_stream << "found unexpected optical bounds (red pixel) on left border " 516 << "at y=" << range.start + 1; 517 return {}; 518 } 519 520 HorizontalImageLine bottom_row(rows, 0, height - 1, width); 521 if (!FillRanges(&bottom_row, color_validator.get(), &horizontal_padding, 522 &horizontal_layout_bounds, out_err)) { 523 return {}; 524 } 525 526 if (!PopulateBounds(horizontal_padding, horizontal_layout_bounds, 527 nine_patch->horizontal_stretch_regions, width - 2, 528 &nine_patch->padding.left, &nine_patch->padding.right, 529 &nine_patch->layout_bounds.left, 530 &nine_patch->layout_bounds.right, "bottom", out_err)) { 531 return {}; 532 } 533 534 VerticalImageLine right_col(rows, width - 1, 0, height); 535 if (!FillRanges(&right_col, color_validator.get(), &vertical_padding, 536 &vertical_layout_bounds, out_err)) { 537 return {}; 538 } 539 540 if (!PopulateBounds(vertical_padding, vertical_layout_bounds, 541 nine_patch->vertical_stretch_regions, height - 2, 542 &nine_patch->padding.top, &nine_patch->padding.bottom, 543 &nine_patch->layout_bounds.top, 544 &nine_patch->layout_bounds.bottom, "right", out_err)) { 545 return {}; 546 } 547 548 // Fill the region colors of the 9-patch. 549 const int32_t num_rows = 550 CalculateSegmentCount(nine_patch->horizontal_stretch_regions, width - 2); 551 const int32_t num_cols = 552 CalculateSegmentCount(nine_patch->vertical_stretch_regions, height - 2); 553 if ((int64_t)num_rows * (int64_t)num_cols > 0x7f) { 554 *out_err = "too many regions in 9-patch"; 555 return {}; 556 } 557 558 nine_patch->region_colors.reserve(num_rows * num_cols); 559 CalculateRegionColors(rows, nine_patch->horizontal_stretch_regions, 560 nine_patch->vertical_stretch_regions, width - 2, 561 height - 2, &nine_patch->region_colors); 562 563 // Compute the outline based on opacity. 564 565 // Find left and right extent of 9-patch content on center row. 566 HorizontalImageLine mid_row(rows, 1, height / 2, width - 2); 567 FindOutlineInsets(&mid_row, &nine_patch->outline.left, 568 &nine_patch->outline.right); 569 570 // Find top and bottom extent of 9-patch content on center column. 571 VerticalImageLine mid_col(rows, width / 2, 1, height - 2); 572 FindOutlineInsets(&mid_col, &nine_patch->outline.top, 573 &nine_patch->outline.bottom); 574 575 const int32_t outline_width = 576 (width - 2) - nine_patch->outline.left - nine_patch->outline.right; 577 const int32_t outline_height = 578 (height - 2) - nine_patch->outline.top - nine_patch->outline.bottom; 579 580 // Find the largest alpha value within the outline area. 581 HorizontalImageLine outline_mid_row( 582 rows, 1 + nine_patch->outline.left, 583 1 + nine_patch->outline.top + (outline_height / 2), outline_width); 584 VerticalImageLine outline_mid_col( 585 rows, 1 + nine_patch->outline.left + (outline_width / 2), 586 1 + nine_patch->outline.top, outline_height); 587 nine_patch->outline_alpha = 588 std::max(FindMaxAlpha(&outline_mid_row), FindMaxAlpha(&outline_mid_col)); 589 590 // Assuming the image is a round rect, compute the radius by marching 591 // diagonally from the top left corner towards the center. 592 DiagonalImageLine diagonal(rows, 1 + nine_patch->outline.left, 593 1 + nine_patch->outline.top, 1, 1, 594 std::min(outline_width, outline_height)); 595 int32_t top_left, bottom_right; 596 FindOutlineInsets(&diagonal, &top_left, &bottom_right); 597 598 /* Determine source radius based upon inset: 599 * sqrt(r^2 + r^2) = sqrt(i^2 + i^2) + r 600 * sqrt(2) * r = sqrt(2) * i + r 601 * (sqrt(2) - 1) * r = sqrt(2) * i 602 * r = sqrt(2) / (sqrt(2) - 1) * i 603 */ 604 nine_patch->outline_radius = 3.4142f * top_left; 605 return nine_patch; 606 } 607 608 std::unique_ptr<uint8_t[]> NinePatch::SerializeBase(size_t* outLen) const { 609 android::Res_png_9patch data; 610 data.numXDivs = static_cast<uint8_t>(horizontal_stretch_regions.size()) * 2; 611 data.numYDivs = static_cast<uint8_t>(vertical_stretch_regions.size()) * 2; 612 data.numColors = static_cast<uint8_t>(region_colors.size()); 613 data.paddingLeft = padding.left; 614 data.paddingRight = padding.right; 615 data.paddingTop = padding.top; 616 data.paddingBottom = padding.bottom; 617 618 auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[data.serializedSize()]); 619 android::Res_png_9patch::serialize( 620 data, (const int32_t*)horizontal_stretch_regions.data(), 621 (const int32_t*)vertical_stretch_regions.data(), region_colors.data(), 622 buffer.get()); 623 // Convert to file endianness. 624 reinterpret_cast<android::Res_png_9patch*>(buffer.get())->deviceToFile(); 625 626 *outLen = data.serializedSize(); 627 return buffer; 628 } 629 630 std::unique_ptr<uint8_t[]> NinePatch::SerializeLayoutBounds( 631 size_t* out_len) const { 632 size_t chunk_len = sizeof(uint32_t) * 4; 633 auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[chunk_len]); 634 uint8_t* cursor = buffer.get(); 635 636 memcpy(cursor, &layout_bounds.left, sizeof(layout_bounds.left)); 637 cursor += sizeof(layout_bounds.left); 638 639 memcpy(cursor, &layout_bounds.top, sizeof(layout_bounds.top)); 640 cursor += sizeof(layout_bounds.top); 641 642 memcpy(cursor, &layout_bounds.right, sizeof(layout_bounds.right)); 643 cursor += sizeof(layout_bounds.right); 644 645 memcpy(cursor, &layout_bounds.bottom, sizeof(layout_bounds.bottom)); 646 cursor += sizeof(layout_bounds.bottom); 647 648 *out_len = chunk_len; 649 return buffer; 650 } 651 652 std::unique_ptr<uint8_t[]> NinePatch::SerializeRoundedRectOutline( 653 size_t* out_len) const { 654 size_t chunk_len = sizeof(uint32_t) * 6; 655 auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[chunk_len]); 656 uint8_t* cursor = buffer.get(); 657 658 memcpy(cursor, &outline.left, sizeof(outline.left)); 659 cursor += sizeof(outline.left); 660 661 memcpy(cursor, &outline.top, sizeof(outline.top)); 662 cursor += sizeof(outline.top); 663 664 memcpy(cursor, &outline.right, sizeof(outline.right)); 665 cursor += sizeof(outline.right); 666 667 memcpy(cursor, &outline.bottom, sizeof(outline.bottom)); 668 cursor += sizeof(outline.bottom); 669 670 *((float*)cursor) = outline_radius; 671 cursor += sizeof(outline_radius); 672 673 *((uint32_t*)cursor) = outline_alpha; 674 675 *out_len = chunk_len; 676 return buffer; 677 } 678 679 ::std::ostream& operator<<(::std::ostream& out, const Range& range) { 680 return out << "[" << range.start << ", " << range.end << ")"; 681 } 682 683 ::std::ostream& operator<<(::std::ostream& out, const Bounds& bounds) { 684 return out << "l=" << bounds.left << " t=" << bounds.top 685 << " r=" << bounds.right << " b=" << bounds.bottom; 686 } 687 688 ::std::ostream& operator<<(::std::ostream& out, const NinePatch& nine_patch) { 689 return out << "horizontalStretch:" 690 << util::Joiner(nine_patch.horizontal_stretch_regions, " ") 691 << " verticalStretch:" 692 << util::Joiner(nine_patch.vertical_stretch_regions, " ") 693 << " padding: " << nine_patch.padding 694 << ", bounds: " << nine_patch.layout_bounds 695 << ", outline: " << nine_patch.outline 696 << " rad=" << nine_patch.outline_radius 697 << " alpha=" << nine_patch.outline_alpha; 698 } 699 700 } // namespace aapt 701