• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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    */
IsValidColor(uint32_t color) const66   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>
FillRanges(const ImageLine * image_line,const ColorValidator * color_validator,std::vector<Range> * primary_ranges,std::vector<Range> * secondary_ranges,std::string * out_err)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:
HorizontalImageLine(uint8_t ** rows,int32_t xoffset,int32_t yoffset,int32_t length)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 
GetLength() const140   inline int32_t GetLength() const { return length_; }
141 
GetColor(int32_t idx) const142   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:
VerticalImageLine(uint8_t ** rows,int32_t xoffset,int32_t yoffset,int32_t length)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 
GetLength() const163   inline int32_t GetLength() const { return length_; }
164 
GetColor(int32_t idx) const165   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:
DiagonalImageLine(uint8_t ** rows,int32_t xoffset,int32_t yoffset,int32_t xstep,int32_t ystep,int32_t length)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 
GetLength() const187   inline int32_t GetLength() const { return length_; }
188 
GetColor(int32_t idx) const189   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:
IsNeutralColor(uint32_t color) const203   bool IsNeutralColor(uint32_t color) const override {
204     return get_alpha(color) == 0;
205   }
206 };
207 
208 class WhiteNeutralColorValidator : public ColorValidator {
209  public:
IsNeutralColor(uint32_t color) const210   bool IsNeutralColor(uint32_t color) const override {
211     return color == kColorOpaqueWhite;
212   }
213 };
214 
get_alpha(uint32_t color)215 inline static uint32_t get_alpha(uint32_t color) {
216   return (color & 0xff000000u) >> 24;
217 }
218 
PopulateBounds(const std::vector<Range> & padding,const std::vector<Range> & layout_bounds,const std::vector<Range> & stretch_regions,const int32_t length,int32_t * padding_start,int32_t * padding_end,int32_t * layout_start,int32_t * layout_end,const StringPiece & edge_name,std::string * out_err)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 
CalculateSegmentCount(const std::vector<Range> & stretch_regions,int32_t length)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 
GetRegionColor(uint8_t ** rows,const Bounds & region)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.
CalculateRegionColors(uint8_t ** rows,const std::vector<Range> & horizontal_stretch_regions,const std::vector<Range> & vertical_stretch_regions,const int32_t width,const int32_t height,std::vector<uint32_t> * out_colors)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>
FindOutlineInsets(const ImageLine * image_line,int32_t * out_start,int32_t * out_end)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>
FindMaxAlpha(const ImageLine * image_line)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).
PackRGBA(const uint8_t * pixel)456 uint32_t NinePatch::PackRGBA(const uint8_t* pixel) {
457   return (pixel[3] << 24) | (pixel[0] << 16) | (pixel[1] << 8) | pixel[2];
458 }
459 
Create(uint8_t ** rows,const int32_t width,const int32_t height,std::string * out_err)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 
SerializeBase(size_t * outLen) const608 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 
SerializeLayoutBounds(size_t * out_len) const630 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 
SerializeRoundedRectOutline(size_t * out_len) const652 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 
operator <<(::std::ostream & out,const Range & range)679 ::std::ostream& operator<<(::std::ostream& out, const Range& range) {
680   return out << "[" << range.start << ", " << range.end << ")";
681 }
682 
operator <<(::std::ostream & out,const Bounds & bounds)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 
operator <<(::std::ostream & out,const NinePatch & nine_patch)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