Home | History | Annotate | Download | only in compile
      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