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
28using android::StringPiece;
29
30namespace aapt {
31
32// Colors in the format 0xAARRGGBB (the way 9-patch expects it).
33constexpr static const uint32_t kColorOpaqueWhite = 0xffffffffu;
34constexpr static const uint32_t kColorOpaqueBlack = 0xff000000u;
35constexpr static const uint32_t kColorOpaqueRed = 0xffff0000u;
36
37constexpr static const uint32_t kPrimaryColor = kColorOpaqueBlack;
38constexpr static const uint32_t kSecondaryColor = kColorOpaqueRed;
39
40/**
41 * Returns the alpha value encoded in the 0xAARRGBB encoded pixel.
42 */
43static 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 */
52class 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//
92template <typename ImageLine>
93static 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 */
134class 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 */
157class 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
176class 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
201class TransparentNeutralColorValidator : public ColorValidator {
202 public:
203  bool IsNeutralColor(uint32_t color) const override {
204    return get_alpha(color) == 0;
205  }
206};
207
208class WhiteNeutralColorValidator : public ColorValidator {
209 public:
210  bool IsNeutralColor(uint32_t color) const override {
211    return color == kColorOpaqueWhite;
212  }
213};
214
215inline static uint32_t get_alpha(uint32_t color) {
216  return (color & 0xff000000u) >> 24;
217}
218
219static 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
285static 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
302static 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.
341static 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).
405template <typename ImageLine>
406static 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
442template <typename ImageLine>
443static 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).
456uint32_t NinePatch::PackRGBA(const uint8_t* pixel) {
457  return (pixel[3] << 24) | (pixel[0] << 16) | (pixel[1] << 8) | pixel[2];
458}
459
460std::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
608std::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
630std::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
652std::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