Represent sticker contours as vector path.

This commit is contained in:
levlam 2020-12-02 18:45:06 +03:00
parent dcf5b2a78b
commit 1da1a14255
5 changed files with 270 additions and 23 deletions

View File

@ -233,6 +233,10 @@ maskPointChin = MaskPoint;
maskPosition point:MaskPoint x_shift:double y_shift:double scale:double = MaskPosition;
//@description Represents a closed vector path. The path begins at the end point of the last command @commands List of vector path commands
closedVectorPath commands:vector<VectorPathCommand> = ClosedVectorPath;
//@description Describes one answer option of a poll @text Option text, 1-100 characters @voter_count Number of voters for this option, available only for closed or voted polls @vote_percentage The percentage of votes for this option, 0-100
//@is_chosen True, if the option was chosen by the user @is_being_chosen True, if the option is being chosen by a pending setPollAnswer request
pollOption text:string voter_count:int32 vote_percentage:int32 is_chosen:Bool is_being_chosen:Bool = PollOption;
@ -270,8 +274,8 @@ photo has_stickers:Bool minithumbnail:minithumbnail sizes:vector<photoSize> = Ph
//@description Describes a sticker @set_id The identifier of the sticker set to which the sticker belongs; 0 if none @width Sticker width; as defined by the sender @height Sticker height; as defined by the sender
//@emoji Emoji corresponding to the sticker @is_animated True, if the sticker is an animated sticker in TGS format @is_mask True, if the sticker is a mask @mask_position Position where the mask should be placed; may be null
//@cover Sticker cover in an SVG format; may be empty @thumbnail Sticker thumbnail in WEBP or JPEG format; may be null @sticker File containing the sticker
sticker set_id:int64 width:int32 height:int32 emoji:string is_animated:Bool is_mask:Bool mask_position:maskPosition cover:string thumbnail:thumbnail sticker:file = Sticker;
//@contours Sticker contours; may be empty. The coordinate system origin is in the upper-left corner @thumbnail Sticker thumbnail in WEBP or JPEG format; may be null @sticker File containing the sticker
sticker set_id:int64 width:int32 height:int32 emoji:string is_animated:Bool is_mask:Bool mask_position:maskPosition contours:vector<closedVectorPath> thumbnail:thumbnail sticker:file = Sticker;
//@description Describes a video file @duration Duration of the video, in seconds; as defined by the sender @width Video width; as defined by the sender @height Video height; as defined by the sender
//@file_name Original name of the file; as defined by the sender @mime_type MIME type of the file; as defined by the sender
@ -3184,6 +3188,19 @@ chatStatisticsChannel period:dateRange member_count:statisticalValue mean_view_c
messageStatistics message_interaction_graph:StatisticalGraph = MessageStatistics;
//@description A point on a Cartesian plane @x The point first coordinate @y The point second coordinate
point x:double y:double = Point;
//@class VectorPathCommand @description Reperesents a vector path command
//@description A straight line to a given point @end_point The end point of the straight line
vectorPathCommandLine end_point:point = VectorPathCommand;
//@description A cubic Bézier curve to a given point @start_control_point The start control point of the curve @end_control_point The end control point of the curve @end_point The end point of the curve
vectorPathCommandCubicBezierCurve start_control_point:point end_control_point:point end_point:point = VectorPathCommand;
//@class Update @description Contains notifications about data changes
//@description The user authorization state has changed @authorization_state New authorization state

Binary file not shown.

View File

@ -877,6 +877,10 @@ tl_object_ptr<td_api::photoSize> copy(const td_api::photoSize &obj) {
vector<int32>(obj.progressive_sizes_));
}
static tl_object_ptr<td_api::photoSize> copy_photo_size(const tl_object_ptr<td_api::photoSize> &obj) {
return copy(obj);
}
template <>
tl_object_ptr<td_api::thumbnail> copy(const td_api::thumbnail &obj) {
auto format = [&]() -> td_api::object_ptr<td_api::ThumbnailFormat> {
@ -902,10 +906,6 @@ tl_object_ptr<td_api::thumbnail> copy(const td_api::thumbnail &obj) {
return make_tl_object<td_api::thumbnail>(std::move(format), obj.width_, obj.height_, copy(obj.file_));
}
static tl_object_ptr<td_api::photoSize> copy_photo_size(const tl_object_ptr<td_api::photoSize> &obj) {
return copy(obj);
}
template <>
tl_object_ptr<td_api::MaskPoint> copy(const td_api::MaskPoint &obj) {
switch (obj.get_id()) {
@ -928,6 +928,44 @@ tl_object_ptr<td_api::maskPosition> copy(const td_api::maskPosition &obj) {
return make_tl_object<td_api::maskPosition>(copy(obj.point_), obj.x_shift_, obj.y_shift_, obj.scale_);
}
template <>
tl_object_ptr<td_api::point> copy(const td_api::point &obj) {
return make_tl_object<td_api::point>(obj.x_, obj.y_);
}
template <>
tl_object_ptr<td_api::VectorPathCommand> copy(const td_api::VectorPathCommand &obj) {
switch (obj.get_id()) {
case td_api::vectorPathCommandLine::ID: {
auto &command = static_cast<const td_api::vectorPathCommandLine &>(obj);
return make_tl_object<td_api::vectorPathCommandLine>(copy(command.end_point_));
}
case td_api::vectorPathCommandCubicBezierCurve::ID: {
auto &command = static_cast<const td_api::vectorPathCommandCubicBezierCurve &>(obj);
return make_tl_object<td_api::vectorPathCommandCubicBezierCurve>(
copy(command.start_control_point_), copy(command.end_control_point_), copy(command.end_point_));
}
default:
UNREACHABLE();
return nullptr;
}
}
static tl_object_ptr<td_api::VectorPathCommand> copy_vector_path_command(
const tl_object_ptr<td_api::VectorPathCommand> &obj) {
return copy(obj);
}
template <>
tl_object_ptr<td_api::closedVectorPath> copy(const td_api::closedVectorPath &obj) {
return make_tl_object<td_api::closedVectorPath>(transform(obj.commands_, copy_vector_path_command));
}
static tl_object_ptr<td_api::closedVectorPath> copy_closed_vector_path(
const tl_object_ptr<td_api::closedVectorPath> &obj) {
return copy(obj);
}
template <>
tl_object_ptr<td_api::animation> copy(const td_api::animation &obj) {
return make_tl_object<td_api::animation>(obj.duration_, obj.width_, obj.height_, obj.file_name_, obj.mime_type_,
@ -956,9 +994,9 @@ tl_object_ptr<td_api::photo> copy(const td_api::photo &obj) {
template <>
tl_object_ptr<td_api::sticker> copy(const td_api::sticker &obj) {
return make_tl_object<td_api::sticker>(obj.set_id_, obj.width_, obj.height_, obj.emoji_, obj.is_animated_,
obj.is_mask_, copy(obj.mask_position_), obj.cover_, copy(obj.thumbnail_),
copy(obj.sticker_));
return make_tl_object<td_api::sticker>(
obj.set_id_, obj.width_, obj.height_, obj.emoji_, obj.is_animated_, obj.is_mask_, copy(obj.mask_position_),
transform(obj.contours_, copy_closed_vector_path), copy(obj.thumbnail_), copy(obj.sticker_));
}
template <>

View File

@ -1334,22 +1334,15 @@ tl_object_ptr<td_api::MaskPoint> StickersManager::get_mask_point_object(int32 po
}
}
string StickersManager::get_sticker_minithumbnail(const string &path) {
vector<td_api::object_ptr<td_api::closedVectorPath>> StickersManager::get_sticker_minithumbnail(CSlice path) {
if (path.empty()) {
return string();
return {};
}
const auto prefix = Slice(
"<?xml version=\"1.0\" encoding=\"utf-8\"?><svg version=\"1.1\" id=\"Layer_1\" "
"xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\" "
"viewBox=\"0 0 512 512\" style=\"enable-background:new 0 0 512 512;\" xml:space=\"preserve\"><style "
"type=\"text/css\">.st0{fill:#E8E8E8;}</style><path class=\"st0\" d=\"M");
const auto suffix = Slice("z\"/></svg>");
auto buf = StackAllocator::alloc(1 << 7);
auto buf = StackAllocator::alloc(1 << 9);
StringBuilder sb(buf.as_slice(), true);
sb << prefix;
sb << 'M';
for (unsigned char c : path) {
if (c >= 128 + 64) {
sb << "AACAAAAHAAALMAAAQASTAVAAAZaacaaaahaaalmaaaqastava.az0123456789-,"[c - 128 - 64];
@ -1362,10 +1355,209 @@ string StickersManager::get_sticker_minithumbnail(const string &path) {
sb << (c & 63);
}
}
sb << suffix;
sb << 'z';
CHECK(!sb.is_error());
return sb.as_cslice().str();
path = sb.as_cslice();
size_t pos = 0;
auto skip_commas = [&path, &pos] {
while (path[pos] == ',') {
pos++;
}
};
auto get_number = [&] {
skip_commas();
int sign = 1;
if (path[pos] == '-') {
sign = -1;
pos++;
}
double res = 0;
while (is_digit(path[pos])) {
res = res * 10 + path[pos++] - '0';
}
if (path[pos] == '.') {
pos++;
double mul = 0.1;
while (is_digit(path[pos])) {
res += (path[pos] - '0') * mul;
mul *= 0.1;
pos++;
}
}
return sign * res;
};
auto make_point = [](double x, double y) {
return td_api::make_object<td_api::point>(x, y);
};
vector<td_api::object_ptr<td_api::closedVectorPath>> result;
double x = 0;
double y = 0;
while (path[pos] != '\0') {
skip_commas();
if (path[pos] == '\0') {
break;
}
if (path[pos] == 'm') {
pos++;
x += get_number();
y += get_number();
} else if (path[pos] == 'M') {
pos++;
x = get_number();
y = get_number();
}
double start_x = x;
double start_y = y;
vector<td_api::object_ptr<td_api::VectorPathCommand>> commands;
bool have_last_end_control_point = false;
double last_end_control_point_x = 0;
double last_end_control_point_y = 0;
bool is_closed = false;
char command = '\0';
while (!is_closed) {
skip_commas();
if (path[pos] == '\0') {
LOG(ERROR) << "Receive unclosed path " << path;
return {};
}
if (is_alpha(path[pos])) {
command = path[pos++];
}
switch (command) {
case 'l':
case 'L':
case 'h':
case 'H':
case 'v':
case 'V':
if (command == 'l' || command == 'h') {
x += get_number();
} else if (command == 'L' || command == 'H') {
x = get_number();
}
if (command == 'l' || command == 'v') {
y += get_number();
} else if (command == 'L' || command == 'V') {
y = get_number();
}
commands.push_back(td_api::make_object<td_api::vectorPathCommandLine>(make_point(x, y)));
have_last_end_control_point = false;
break;
case 'C':
case 'c':
case 'S':
case 's': {
double start_control_point_x;
double start_control_point_y;
if (command == 'S' || command == 's') {
if (have_last_end_control_point) {
start_control_point_x = 2 * x - last_end_control_point_x;
start_control_point_y = 2 * y - last_end_control_point_y;
} else {
start_control_point_x = x;
start_control_point_y = y;
}
} else {
start_control_point_x = get_number();
start_control_point_y = get_number();
if (command == 'c') {
start_control_point_x += x;
start_control_point_y += y;
}
}
last_end_control_point_x = get_number();
last_end_control_point_y = get_number();
if (command == 'c' || command == 's') {
last_end_control_point_x += x;
last_end_control_point_y += y;
}
have_last_end_control_point = true;
if (command == 'c' || command == 's') {
x += get_number();
y += get_number();
} else {
x = get_number();
y = get_number();
}
commands.push_back(td_api::make_object<td_api::vectorPathCommandCubicBezierCurve>(
make_point(start_control_point_x, start_control_point_y),
make_point(last_end_control_point_x, last_end_control_point_y), make_point(x, y)));
break;
}
case 'z':
case 'Z':
if (x != start_x || y != start_y) {
x = start_x;
y = start_y;
commands.push_back(td_api::make_object<td_api::vectorPathCommandLine>(make_point(x, y)));
}
if (!commands.empty()) {
result.push_back(td_api::make_object<td_api::closedVectorPath>(std::move(commands)));
}
is_closed = true;
break;
default:
LOG(ERROR) << "Receive invalid command " << command << " in " << path;
return {};
}
}
}
/*
string svg;
for (const auto &vector_path : result) {
CHECK(!vector_path->commands_.empty());
svg += 'M';
auto add_point = [&](const td_api::object_ptr<td_api::point> &p) {
svg += to_string(static_cast<int>(p->x_));
svg += ',';
svg += to_string(static_cast<int>(p->y_));
svg += ',';
};
auto last_command = vector_path->commands_.back().get();
switch (last_command->get_id()) {
case td_api::vectorPathCommandLine::ID:
add_point(static_cast<const td_api::vectorPathCommandLine *>(last_command)->end_point_);
break;
case td_api::vectorPathCommandCubicBezierCurve::ID:
add_point(static_cast<const td_api::vectorPathCommandCubicBezierCurve *>(last_command)->end_point_);
break;
default:
UNREACHABLE();
}
for (auto &command : vector_path->commands_) {
switch (command->get_id()) {
case td_api::vectorPathCommandLine::ID: {
auto line = static_cast<const td_api::vectorPathCommandLine *>(command.get());
svg += 'L';
add_point(line->end_point_);
break;
}
case td_api::vectorPathCommandCubicBezierCurve::ID: {
auto curve = static_cast<const td_api::vectorPathCommandCubicBezierCurve *>(command.get());
svg += 'C';
add_point(curve->start_control_point_);
add_point(curve->end_control_point_);
add_point(curve->end_point_);
break;
}
default:
UNREACHABLE();
}
}
svg += 'z';
}
*/
return result;
}
tl_object_ptr<td_api::sticker> StickersManager::get_sticker_object(FileId file_id) const {

View File

@ -393,7 +393,7 @@ class StickersManager : public Actor {
class UploadStickerFileCallback;
static string get_sticker_minithumbnail(const string &path);
static vector<td_api::object_ptr<td_api::closedVectorPath>> get_sticker_minithumbnail(CSlice path);
static tl_object_ptr<td_api::MaskPoint> get_mask_point_object(int32 point);