You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
701 lines
23 KiB
701 lines
23 KiB
/*
|
|
* Copyright (C) 2016 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "compile/Image.h"
|
|
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "androidfw/ResourceTypes.h"
|
|
#include "androidfw/StringPiece.h"
|
|
|
|
#include "util/Util.h"
|
|
|
|
using android::StringPiece;
|
|
|
|
namespace aapt {
|
|
|
|
// Colors in the format 0xAARRGGBB (the way 9-patch expects it).
|
|
constexpr static const uint32_t kColorOpaqueWhite = 0xffffffffu;
|
|
constexpr static const uint32_t kColorOpaqueBlack = 0xff000000u;
|
|
constexpr static const uint32_t kColorOpaqueRed = 0xffff0000u;
|
|
|
|
constexpr static const uint32_t kPrimaryColor = kColorOpaqueBlack;
|
|
constexpr static const uint32_t kSecondaryColor = kColorOpaqueRed;
|
|
|
|
/**
|
|
* Returns the alpha value encoded in the 0xAARRGBB encoded pixel.
|
|
*/
|
|
static uint32_t get_alpha(uint32_t color);
|
|
|
|
/**
|
|
* Determines whether a color on an ImageLine is valid.
|
|
* A 9patch image may use a transparent color as neutral,
|
|
* or a fully opaque white color as neutral, based on the
|
|
* pixel color at (0,0) of the image. One or the other is fine,
|
|
* but we need to ensure consistency throughout the image.
|
|
*/
|
|
class ColorValidator {
|
|
public:
|
|
virtual ~ColorValidator() = default;
|
|
|
|
/**
|
|
* Returns true if the color specified is a neutral color
|
|
* (no padding, stretching, or optical bounds).
|
|
*/
|
|
virtual bool IsNeutralColor(uint32_t color) const = 0;
|
|
|
|
/**
|
|
* Returns true if the color is either a neutral color
|
|
* or one denoting padding, stretching, or optical bounds.
|
|
*/
|
|
bool IsValidColor(uint32_t color) const {
|
|
switch (color) {
|
|
case kPrimaryColor:
|
|
case kSecondaryColor:
|
|
return true;
|
|
}
|
|
return IsNeutralColor(color);
|
|
}
|
|
};
|
|
|
|
// Walks an ImageLine and records Ranges of primary and secondary colors.
|
|
// The primary color is black and is used to denote a padding or stretching
|
|
// range,
|
|
// depending on which border we're iterating over.
|
|
// The secondary color is red and is used to denote optical bounds.
|
|
//
|
|
// An ImageLine is a templated-interface that would look something like this if
|
|
// it
|
|
// were polymorphic:
|
|
//
|
|
// class ImageLine {
|
|
// public:
|
|
// virtual int32_t GetLength() const = 0;
|
|
// virtual uint32_t GetColor(int32_t idx) const = 0;
|
|
// };
|
|
//
|
|
template <typename ImageLine>
|
|
static bool FillRanges(const ImageLine* image_line,
|
|
const ColorValidator* color_validator,
|
|
std::vector<Range>* primary_ranges,
|
|
std::vector<Range>* secondary_ranges,
|
|
std::string* out_err) {
|
|
const int32_t length = image_line->GetLength();
|
|
|
|
uint32_t last_color = 0xffffffffu;
|
|
for (int32_t idx = 1; idx < length - 1; idx++) {
|
|
const uint32_t color = image_line->GetColor(idx);
|
|
if (!color_validator->IsValidColor(color)) {
|
|
*out_err = "found an invalid color";
|
|
return false;
|
|
}
|
|
|
|
if (color != last_color) {
|
|
// We are ending a range. Which range?
|
|
// note: encode the x offset without the final 1 pixel border.
|
|
if (last_color == kPrimaryColor) {
|
|
primary_ranges->back().end = idx - 1;
|
|
} else if (last_color == kSecondaryColor) {
|
|
secondary_ranges->back().end = idx - 1;
|
|
}
|
|
|
|
// We are starting a range. Which range?
|
|
// note: encode the x offset without the final 1 pixel border.
|
|
if (color == kPrimaryColor) {
|
|
primary_ranges->push_back(Range(idx - 1, length - 2));
|
|
} else if (color == kSecondaryColor) {
|
|
secondary_ranges->push_back(Range(idx - 1, length - 2));
|
|
}
|
|
last_color = color;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Iterates over a row in an image. Implements the templated ImageLine
|
|
* interface.
|
|
*/
|
|
class HorizontalImageLine {
|
|
public:
|
|
explicit HorizontalImageLine(uint8_t** rows, int32_t xoffset, int32_t yoffset,
|
|
int32_t length)
|
|
: rows_(rows), xoffset_(xoffset), yoffset_(yoffset), length_(length) {}
|
|
|
|
inline int32_t GetLength() const { return length_; }
|
|
|
|
inline uint32_t GetColor(int32_t idx) const {
|
|
return NinePatch::PackRGBA(rows_[yoffset_] + (idx + xoffset_) * 4);
|
|
}
|
|
|
|
private:
|
|
uint8_t** rows_;
|
|
int32_t xoffset_, yoffset_, length_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(HorizontalImageLine);
|
|
};
|
|
|
|
/**
|
|
* Iterates over a column in an image. Implements the templated ImageLine
|
|
* interface.
|
|
*/
|
|
class VerticalImageLine {
|
|
public:
|
|
explicit VerticalImageLine(uint8_t** rows, int32_t xoffset, int32_t yoffset,
|
|
int32_t length)
|
|
: rows_(rows), xoffset_(xoffset), yoffset_(yoffset), length_(length) {}
|
|
|
|
inline int32_t GetLength() const { return length_; }
|
|
|
|
inline uint32_t GetColor(int32_t idx) const {
|
|
return NinePatch::PackRGBA(rows_[yoffset_ + idx] + (xoffset_ * 4));
|
|
}
|
|
|
|
private:
|
|
uint8_t** rows_;
|
|
int32_t xoffset_, yoffset_, length_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(VerticalImageLine);
|
|
};
|
|
|
|
class DiagonalImageLine {
|
|
public:
|
|
explicit DiagonalImageLine(uint8_t** rows, int32_t xoffset, int32_t yoffset,
|
|
int32_t xstep, int32_t ystep, int32_t length)
|
|
: rows_(rows),
|
|
xoffset_(xoffset),
|
|
yoffset_(yoffset),
|
|
xstep_(xstep),
|
|
ystep_(ystep),
|
|
length_(length) {}
|
|
|
|
inline int32_t GetLength() const { return length_; }
|
|
|
|
inline uint32_t GetColor(int32_t idx) const {
|
|
return NinePatch::PackRGBA(rows_[yoffset_ + (idx * ystep_)] +
|
|
((idx + xoffset_) * xstep_) * 4);
|
|
}
|
|
|
|
private:
|
|
uint8_t** rows_;
|
|
int32_t xoffset_, yoffset_, xstep_, ystep_, length_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(DiagonalImageLine);
|
|
};
|
|
|
|
class TransparentNeutralColorValidator : public ColorValidator {
|
|
public:
|
|
bool IsNeutralColor(uint32_t color) const override {
|
|
return get_alpha(color) == 0;
|
|
}
|
|
};
|
|
|
|
class WhiteNeutralColorValidator : public ColorValidator {
|
|
public:
|
|
bool IsNeutralColor(uint32_t color) const override {
|
|
return color == kColorOpaqueWhite;
|
|
}
|
|
};
|
|
|
|
inline static uint32_t get_alpha(uint32_t color) {
|
|
return (color & 0xff000000u) >> 24;
|
|
}
|
|
|
|
static bool 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) {
|
|
if (padding.size() > 1) {
|
|
std::stringstream err_stream;
|
|
err_stream << "too many padding sections on " << edge_name << " border";
|
|
*out_err = err_stream.str();
|
|
return false;
|
|
}
|
|
|
|
*padding_start = 0;
|
|
*padding_end = 0;
|
|
if (!padding.empty()) {
|
|
const Range& range = padding.front();
|
|
*padding_start = range.start;
|
|
*padding_end = length - range.end;
|
|
} else if (!stretch_regions.empty()) {
|
|
// No padding was defined. Compute the padding from the first and last
|
|
// stretch regions.
|
|
*padding_start = stretch_regions.front().start;
|
|
*padding_end = length - stretch_regions.back().end;
|
|
}
|
|
|
|
if (layout_bounds.size() > 2) {
|
|
std::stringstream err_stream;
|
|
err_stream << "too many layout bounds sections on " << edge_name
|
|
<< " border";
|
|
*out_err = err_stream.str();
|
|
return false;
|
|
}
|
|
|
|
*layout_start = 0;
|
|
*layout_end = 0;
|
|
if (layout_bounds.size() >= 1) {
|
|
const Range& range = layout_bounds.front();
|
|
// If there is only one layout bound segment, it might not start at 0, but
|
|
// then it should
|
|
// end at length.
|
|
if (range.start != 0 && range.end != length) {
|
|
std::stringstream err_stream;
|
|
err_stream << "layout bounds on " << edge_name
|
|
<< " border must start at edge";
|
|
*out_err = err_stream.str();
|
|
return false;
|
|
}
|
|
*layout_start = range.end;
|
|
|
|
if (layout_bounds.size() >= 2) {
|
|
const Range& range = layout_bounds.back();
|
|
if (range.end != length) {
|
|
std::stringstream err_stream;
|
|
err_stream << "layout bounds on " << edge_name
|
|
<< " border must start at edge";
|
|
*out_err = err_stream.str();
|
|
return false;
|
|
}
|
|
*layout_end = length - range.start;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static int32_t CalculateSegmentCount(const std::vector<Range>& stretch_regions,
|
|
int32_t length) {
|
|
if (stretch_regions.size() == 0) {
|
|
return 0;
|
|
}
|
|
|
|
const bool start_is_fixed = stretch_regions.front().start != 0;
|
|
const bool end_is_fixed = stretch_regions.back().end != length;
|
|
int32_t modifier = 0;
|
|
if (start_is_fixed && end_is_fixed) {
|
|
modifier = 1;
|
|
} else if (!start_is_fixed && !end_is_fixed) {
|
|
modifier = -1;
|
|
}
|
|
return static_cast<int32_t>(stretch_regions.size()) * 2 + modifier;
|
|
}
|
|
|
|
static uint32_t GetRegionColor(uint8_t** rows, const Bounds& region) {
|
|
// Sample the first pixel to compare against.
|
|
const uint32_t expected_color =
|
|
NinePatch::PackRGBA(rows[region.top] + region.left * 4);
|
|
for (int32_t y = region.top; y < region.bottom; y++) {
|
|
const uint8_t* row = rows[y];
|
|
for (int32_t x = region.left; x < region.right; x++) {
|
|
const uint32_t color = NinePatch::PackRGBA(row + x * 4);
|
|
if (get_alpha(color) == 0) {
|
|
// The color is transparent.
|
|
// If the expectedColor is not transparent, NO_COLOR.
|
|
if (get_alpha(expected_color) != 0) {
|
|
return android::Res_png_9patch::NO_COLOR;
|
|
}
|
|
} else if (color != expected_color) {
|
|
return android::Res_png_9patch::NO_COLOR;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (get_alpha(expected_color) == 0) {
|
|
return android::Res_png_9patch::TRANSPARENT_COLOR;
|
|
}
|
|
return expected_color;
|
|
}
|
|
|
|
// Fills out_colors with each 9-patch section's color. If the whole section is
|
|
// transparent,
|
|
// it gets the special TRANSPARENT color. If the whole section is the same
|
|
// color, it is assigned
|
|
// that color. Otherwise it gets the special NO_COLOR color.
|
|
//
|
|
// Note that the rows contain the 9-patch 1px border, and the indices in the
|
|
// stretch regions are
|
|
// already offset to exclude the border. This means that each time the rows are
|
|
// accessed,
|
|
// the indices must be offset by 1.
|
|
//
|
|
// width and height also include the 9-patch 1px border.
|
|
static void 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) {
|
|
int32_t next_top = 0;
|
|
Bounds bounds;
|
|
auto row_iter = vertical_stretch_regions.begin();
|
|
while (next_top != height) {
|
|
if (row_iter != vertical_stretch_regions.end()) {
|
|
if (next_top != row_iter->start) {
|
|
// This is a fixed segment.
|
|
// Offset the bounds by 1 to accommodate the border.
|
|
bounds.top = next_top + 1;
|
|
bounds.bottom = row_iter->start + 1;
|
|
next_top = row_iter->start;
|
|
} else {
|
|
// This is a stretchy segment.
|
|
// Offset the bounds by 1 to accommodate the border.
|
|
bounds.top = row_iter->start + 1;
|
|
bounds.bottom = row_iter->end + 1;
|
|
next_top = row_iter->end;
|
|
++row_iter;
|
|
}
|
|
} else {
|
|
// This is the end, fixed section.
|
|
// Offset the bounds by 1 to accommodate the border.
|
|
bounds.top = next_top + 1;
|
|
bounds.bottom = height + 1;
|
|
next_top = height;
|
|
}
|
|
|
|
int32_t next_left = 0;
|
|
auto col_iter = horizontal_stretch_regions.begin();
|
|
while (next_left != width) {
|
|
if (col_iter != horizontal_stretch_regions.end()) {
|
|
if (next_left != col_iter->start) {
|
|
// This is a fixed segment.
|
|
// Offset the bounds by 1 to accommodate the border.
|
|
bounds.left = next_left + 1;
|
|
bounds.right = col_iter->start + 1;
|
|
next_left = col_iter->start;
|
|
} else {
|
|
// This is a stretchy segment.
|
|
// Offset the bounds by 1 to accommodate the border.
|
|
bounds.left = col_iter->start + 1;
|
|
bounds.right = col_iter->end + 1;
|
|
next_left = col_iter->end;
|
|
++col_iter;
|
|
}
|
|
} else {
|
|
// This is the end, fixed section.
|
|
// Offset the bounds by 1 to accommodate the border.
|
|
bounds.left = next_left + 1;
|
|
bounds.right = width + 1;
|
|
next_left = width;
|
|
}
|
|
out_colors->push_back(GetRegionColor(rows, bounds));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Calculates the insets of a row/column of pixels based on where the largest
|
|
// alpha value begins
|
|
// (on both sides).
|
|
template <typename ImageLine>
|
|
static void FindOutlineInsets(const ImageLine* image_line, int32_t* out_start,
|
|
int32_t* out_end) {
|
|
*out_start = 0;
|
|
*out_end = 0;
|
|
|
|
const int32_t length = image_line->GetLength();
|
|
if (length < 3) {
|
|
return;
|
|
}
|
|
|
|
// If the length is odd, we want both sides to process the center pixel,
|
|
// so we use two different midpoints (to account for < and <= in the different
|
|
// loops).
|
|
const int32_t mid2 = length / 2;
|
|
const int32_t mid1 = mid2 + (length % 2);
|
|
|
|
uint32_t max_alpha = 0;
|
|
for (int32_t i = 0; i < mid1 && max_alpha != 0xff; i++) {
|
|
uint32_t alpha = get_alpha(image_line->GetColor(i));
|
|
if (alpha > max_alpha) {
|
|
max_alpha = alpha;
|
|
*out_start = i;
|
|
}
|
|
}
|
|
|
|
max_alpha = 0;
|
|
for (int32_t i = length - 1; i >= mid2 && max_alpha != 0xff; i--) {
|
|
uint32_t alpha = get_alpha(image_line->GetColor(i));
|
|
if (alpha > max_alpha) {
|
|
max_alpha = alpha;
|
|
*out_end = length - (i + 1);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
template <typename ImageLine>
|
|
static uint32_t FindMaxAlpha(const ImageLine* image_line) {
|
|
const int32_t length = image_line->GetLength();
|
|
uint32_t max_alpha = 0;
|
|
for (int32_t idx = 0; idx < length && max_alpha != 0xff; idx++) {
|
|
uint32_t alpha = get_alpha(image_line->GetColor(idx));
|
|
if (alpha > max_alpha) {
|
|
max_alpha = alpha;
|
|
}
|
|
}
|
|
return max_alpha;
|
|
}
|
|
|
|
// Pack the pixels in as 0xAARRGGBB (as 9-patch expects it).
|
|
uint32_t NinePatch::PackRGBA(const uint8_t* pixel) {
|
|
return (pixel[3] << 24) | (pixel[0] << 16) | (pixel[1] << 8) | pixel[2];
|
|
}
|
|
|
|
std::unique_ptr<NinePatch> NinePatch::Create(uint8_t** rows,
|
|
const int32_t width,
|
|
const int32_t height,
|
|
std::string* out_err) {
|
|
if (width < 3 || height < 3) {
|
|
*out_err = "image must be at least 3x3 (1x1 image with 1 pixel border)";
|
|
return {};
|
|
}
|
|
|
|
std::vector<Range> horizontal_padding;
|
|
std::vector<Range> horizontal_layout_bounds;
|
|
std::vector<Range> vertical_padding;
|
|
std::vector<Range> vertical_layout_bounds;
|
|
std::vector<Range> unexpected_ranges;
|
|
std::unique_ptr<ColorValidator> color_validator;
|
|
|
|
if (rows[0][3] == 0) {
|
|
color_validator = util::make_unique<TransparentNeutralColorValidator>();
|
|
} else if (PackRGBA(rows[0]) == kColorOpaqueWhite) {
|
|
color_validator = util::make_unique<WhiteNeutralColorValidator>();
|
|
} else {
|
|
*out_err =
|
|
"top-left corner pixel must be either opaque white or transparent";
|
|
return {};
|
|
}
|
|
|
|
// Private constructor, can't use make_unique.
|
|
auto nine_patch = std::unique_ptr<NinePatch>(new NinePatch());
|
|
|
|
HorizontalImageLine top_row(rows, 0, 0, width);
|
|
if (!FillRanges(&top_row, color_validator.get(),
|
|
&nine_patch->horizontal_stretch_regions, &unexpected_ranges,
|
|
out_err)) {
|
|
return {};
|
|
}
|
|
|
|
if (!unexpected_ranges.empty()) {
|
|
const Range& range = unexpected_ranges[0];
|
|
std::stringstream err_stream;
|
|
err_stream << "found unexpected optical bounds (red pixel) on top border "
|
|
<< "at x=" << range.start + 1;
|
|
*out_err = err_stream.str();
|
|
return {};
|
|
}
|
|
|
|
VerticalImageLine left_col(rows, 0, 0, height);
|
|
if (!FillRanges(&left_col, color_validator.get(),
|
|
&nine_patch->vertical_stretch_regions, &unexpected_ranges,
|
|
out_err)) {
|
|
return {};
|
|
}
|
|
|
|
if (!unexpected_ranges.empty()) {
|
|
const Range& range = unexpected_ranges[0];
|
|
std::stringstream err_stream;
|
|
err_stream << "found unexpected optical bounds (red pixel) on left border "
|
|
<< "at y=" << range.start + 1;
|
|
return {};
|
|
}
|
|
|
|
HorizontalImageLine bottom_row(rows, 0, height - 1, width);
|
|
if (!FillRanges(&bottom_row, color_validator.get(), &horizontal_padding,
|
|
&horizontal_layout_bounds, out_err)) {
|
|
return {};
|
|
}
|
|
|
|
if (!PopulateBounds(horizontal_padding, horizontal_layout_bounds,
|
|
nine_patch->horizontal_stretch_regions, width - 2,
|
|
&nine_patch->padding.left, &nine_patch->padding.right,
|
|
&nine_patch->layout_bounds.left,
|
|
&nine_patch->layout_bounds.right, "bottom", out_err)) {
|
|
return {};
|
|
}
|
|
|
|
VerticalImageLine right_col(rows, width - 1, 0, height);
|
|
if (!FillRanges(&right_col, color_validator.get(), &vertical_padding,
|
|
&vertical_layout_bounds, out_err)) {
|
|
return {};
|
|
}
|
|
|
|
if (!PopulateBounds(vertical_padding, vertical_layout_bounds,
|
|
nine_patch->vertical_stretch_regions, height - 2,
|
|
&nine_patch->padding.top, &nine_patch->padding.bottom,
|
|
&nine_patch->layout_bounds.top,
|
|
&nine_patch->layout_bounds.bottom, "right", out_err)) {
|
|
return {};
|
|
}
|
|
|
|
// Fill the region colors of the 9-patch.
|
|
const int32_t num_rows =
|
|
CalculateSegmentCount(nine_patch->horizontal_stretch_regions, width - 2);
|
|
const int32_t num_cols =
|
|
CalculateSegmentCount(nine_patch->vertical_stretch_regions, height - 2);
|
|
if ((int64_t)num_rows * (int64_t)num_cols > 0x7f) {
|
|
*out_err = "too many regions in 9-patch";
|
|
return {};
|
|
}
|
|
|
|
nine_patch->region_colors.reserve(num_rows * num_cols);
|
|
CalculateRegionColors(rows, nine_patch->horizontal_stretch_regions,
|
|
nine_patch->vertical_stretch_regions, width - 2,
|
|
height - 2, &nine_patch->region_colors);
|
|
|
|
// Compute the outline based on opacity.
|
|
|
|
// Find left and right extent of 9-patch content on center row.
|
|
HorizontalImageLine mid_row(rows, 1, height / 2, width - 2);
|
|
FindOutlineInsets(&mid_row, &nine_patch->outline.left,
|
|
&nine_patch->outline.right);
|
|
|
|
// Find top and bottom extent of 9-patch content on center column.
|
|
VerticalImageLine mid_col(rows, width / 2, 1, height - 2);
|
|
FindOutlineInsets(&mid_col, &nine_patch->outline.top,
|
|
&nine_patch->outline.bottom);
|
|
|
|
const int32_t outline_width =
|
|
(width - 2) - nine_patch->outline.left - nine_patch->outline.right;
|
|
const int32_t outline_height =
|
|
(height - 2) - nine_patch->outline.top - nine_patch->outline.bottom;
|
|
|
|
// Find the largest alpha value within the outline area.
|
|
HorizontalImageLine outline_mid_row(
|
|
rows, 1 + nine_patch->outline.left,
|
|
1 + nine_patch->outline.top + (outline_height / 2), outline_width);
|
|
VerticalImageLine outline_mid_col(
|
|
rows, 1 + nine_patch->outline.left + (outline_width / 2),
|
|
1 + nine_patch->outline.top, outline_height);
|
|
nine_patch->outline_alpha =
|
|
std::max(FindMaxAlpha(&outline_mid_row), FindMaxAlpha(&outline_mid_col));
|
|
|
|
// Assuming the image is a round rect, compute the radius by marching
|
|
// diagonally from the top left corner towards the center.
|
|
DiagonalImageLine diagonal(rows, 1 + nine_patch->outline.left,
|
|
1 + nine_patch->outline.top, 1, 1,
|
|
std::min(outline_width, outline_height));
|
|
int32_t top_left, bottom_right;
|
|
FindOutlineInsets(&diagonal, &top_left, &bottom_right);
|
|
|
|
/* Determine source radius based upon inset:
|
|
* sqrt(r^2 + r^2) = sqrt(i^2 + i^2) + r
|
|
* sqrt(2) * r = sqrt(2) * i + r
|
|
* (sqrt(2) - 1) * r = sqrt(2) * i
|
|
* r = sqrt(2) / (sqrt(2) - 1) * i
|
|
*/
|
|
nine_patch->outline_radius = 3.4142f * top_left;
|
|
return nine_patch;
|
|
}
|
|
|
|
std::unique_ptr<uint8_t[]> NinePatch::SerializeBase(size_t* outLen) const {
|
|
android::Res_png_9patch data;
|
|
data.numXDivs = static_cast<uint8_t>(horizontal_stretch_regions.size()) * 2;
|
|
data.numYDivs = static_cast<uint8_t>(vertical_stretch_regions.size()) * 2;
|
|
data.numColors = static_cast<uint8_t>(region_colors.size());
|
|
data.paddingLeft = padding.left;
|
|
data.paddingRight = padding.right;
|
|
data.paddingTop = padding.top;
|
|
data.paddingBottom = padding.bottom;
|
|
|
|
auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[data.serializedSize()]);
|
|
android::Res_png_9patch::serialize(
|
|
data, (const int32_t*)horizontal_stretch_regions.data(),
|
|
(const int32_t*)vertical_stretch_regions.data(), region_colors.data(),
|
|
buffer.get());
|
|
// Convert to file endianness.
|
|
reinterpret_cast<android::Res_png_9patch*>(buffer.get())->deviceToFile();
|
|
|
|
*outLen = data.serializedSize();
|
|
return buffer;
|
|
}
|
|
|
|
std::unique_ptr<uint8_t[]> NinePatch::SerializeLayoutBounds(
|
|
size_t* out_len) const {
|
|
size_t chunk_len = sizeof(uint32_t) * 4;
|
|
auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[chunk_len]);
|
|
uint8_t* cursor = buffer.get();
|
|
|
|
memcpy(cursor, &layout_bounds.left, sizeof(layout_bounds.left));
|
|
cursor += sizeof(layout_bounds.left);
|
|
|
|
memcpy(cursor, &layout_bounds.top, sizeof(layout_bounds.top));
|
|
cursor += sizeof(layout_bounds.top);
|
|
|
|
memcpy(cursor, &layout_bounds.right, sizeof(layout_bounds.right));
|
|
cursor += sizeof(layout_bounds.right);
|
|
|
|
memcpy(cursor, &layout_bounds.bottom, sizeof(layout_bounds.bottom));
|
|
cursor += sizeof(layout_bounds.bottom);
|
|
|
|
*out_len = chunk_len;
|
|
return buffer;
|
|
}
|
|
|
|
std::unique_ptr<uint8_t[]> NinePatch::SerializeRoundedRectOutline(
|
|
size_t* out_len) const {
|
|
size_t chunk_len = sizeof(uint32_t) * 6;
|
|
auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[chunk_len]);
|
|
uint8_t* cursor = buffer.get();
|
|
|
|
memcpy(cursor, &outline.left, sizeof(outline.left));
|
|
cursor += sizeof(outline.left);
|
|
|
|
memcpy(cursor, &outline.top, sizeof(outline.top));
|
|
cursor += sizeof(outline.top);
|
|
|
|
memcpy(cursor, &outline.right, sizeof(outline.right));
|
|
cursor += sizeof(outline.right);
|
|
|
|
memcpy(cursor, &outline.bottom, sizeof(outline.bottom));
|
|
cursor += sizeof(outline.bottom);
|
|
|
|
*((float*)cursor) = outline_radius;
|
|
cursor += sizeof(outline_radius);
|
|
|
|
*((uint32_t*)cursor) = outline_alpha;
|
|
|
|
*out_len = chunk_len;
|
|
return buffer;
|
|
}
|
|
|
|
::std::ostream& operator<<(::std::ostream& out, const Range& range) {
|
|
return out << "[" << range.start << ", " << range.end << ")";
|
|
}
|
|
|
|
::std::ostream& operator<<(::std::ostream& out, const Bounds& bounds) {
|
|
return out << "l=" << bounds.left << " t=" << bounds.top
|
|
<< " r=" << bounds.right << " b=" << bounds.bottom;
|
|
}
|
|
|
|
::std::ostream& operator<<(::std::ostream& out, const NinePatch& nine_patch) {
|
|
return out << "horizontalStretch:"
|
|
<< util::Joiner(nine_patch.horizontal_stretch_regions, " ")
|
|
<< " verticalStretch:"
|
|
<< util::Joiner(nine_patch.vertical_stretch_regions, " ")
|
|
<< " padding: " << nine_patch.padding
|
|
<< ", bounds: " << nine_patch.layout_bounds
|
|
<< ", outline: " << nine_patch.outline
|
|
<< " rad=" << nine_patch.outline_radius
|
|
<< " alpha=" << nine_patch.outline_alpha;
|
|
}
|
|
|
|
} // namespace aapt
|