浏览代码

New makeshot implementation.

Nikita Kalanakov 2 年之前
父节点
当前提交
dcf0e4708c
共有 5 个文件被更改,包括 537 次插入22 次删除
  1. 0 8
      actions.py
  2. 18 9
      commands.py
  3. 516 0
      makeshot/makeshot.c
  4. 3 0
      makeshot/mk.sh
  5. 0 5
      models.py

+ 0 - 8
actions.py

@@ -62,14 +62,6 @@ async def add_gif(action, file_id):
 async def get_random_gif(action):
   return await action.gifs.all().annotate(order=Random()).order_by('order').first()
 
-async def assign_color(user_id):
-  user_color = await UserColor.filter(user_id=user_id).first()
-  if not user_color:
-    user_color = UserColor(user_id=user_id, color=randint(0, 7))
-    await user_color.save()
-  
-  return user_color.color
-
 async def create_new_pack(bot, sticker):
   last_pack = await StickerPack.all().order_by('-id').first()
   set_id = last_pack.id + 1 if last_pack else 1

+ 18 - 9
commands.py

@@ -1,4 +1,5 @@
 from sys import executable
+from struct import pack
 from asyncio import create_subprocess_shell
 from asyncio.subprocess import PIPE
 from datetime import datetime
@@ -15,7 +16,6 @@ from actions import (
   delete_action,
   add_gif,
   add_sticker,
-  assign_color,
   add_admin,
   delete_admin,
   add_or_update_birthday,
@@ -147,7 +147,7 @@ async def addgif_handler(bot, event, command):
 # Very, very, VERY evil code...
 async def make_message_shot(bot, message):
   proc = await create_subprocess_shell(
-    f'{executable} makeshot.py',
+    f'./makeshot/makeshot',
     stdin=PIPE
   )
 
@@ -164,13 +164,22 @@ async def make_message_shot(bot, message):
 
     full_name = get_display_name(message.sender)
 
-  data = dumps({
-    'output_path': output_path,
-    'avatar_path': avatar_path,
-    'username': full_name if full_name else message.sender.username,
-    'username_color': await assign_color(message.sender.id if message.sender else message.peer_id.channel_id),
-    'text': message.text
-  }).encode('UTF-8')
+  data = bytes()
+
+  data += pack('I', len(output_path))
+  data += bytes(output_path, encoding='ASCII')
+  data += pack('I', len(avatar_path))
+  data += bytes(avatar_path, encoding='ASCII')
+
+  username = bytes(full_name if full_name else message.sender.username, encoding='UTF-8')
+  data += pack('I', len(username))
+  data += username
+
+  data += pack('I', (message.sender.id if message.sender else message.peer_id.channel_id) % 7)
+
+  text = bytes(message.text, encoding='UTF-8')
+  data += pack('I', len(text))
+  data += text
 
   await proc.communicate(input=data) 
 

+ 516 - 0
makeshot/makeshot.c

@@ -0,0 +1,516 @@
+#include <math.h>
+
+#include <cairo/cairo.h>
+#include <pango/pangocairo.h>
+
+#define NICKNAME_TEXT_SIZE  23
+#define MESSAGE_TEXT_SIZE   24
+
+#define HPADDING            18
+#define VPADDING            8
+
+#define AVATAR_OFFSET       3
+#define AVATAR_PADDING      12
+#define AVATAR_DIM          75
+#define AVATAR_WIDTH        (AVATAR_DIM + AVATAR_PADDING)
+
+#define TAIL_WIDTH          18
+#define TAIL_HEIGHT         30
+
+#define MESSAGE_BOX_RADIUS  8
+#define MESSAGE_BOX_MIN     160
+
+#define WRAP_WIDTH          400
+
+#define TAIL_PATH           "./resources/tail.png"
+
+#define MAX(x, y) ((x) >= (y)? (x): (y))
+#define MIN(x, y) ((x) <= (y)? (x): (y))
+
+typedef unsigned int uint_t;
+
+typedef struct {
+  int r, g, b;
+} color_t;
+
+color_t COLORS_TABLE[] = {
+  /* Nickname colors */
+  {0xee, 0x49, 0x28}, /* 0 */
+  {0x41, 0xa9, 0x03}, /* 1 */
+  {0xe0, 0x96, 0x02}, /* 2 */
+  {0x0f, 0x94, 0xed}, /* 3 */
+  {0x8f, 0x3b, 0xf7}, /* 4 */
+  {0xfc, 0x43, 0x80}, /* 5 */
+  {0x00, 0xa1, 0xc4}, /* 6 */
+  {0xeb, 0x70, 0x02}, /* 7 */
+
+  /* Message text color */
+  {0xff, 0xff, 0xff}, /* 8 */
+
+  /* Message box color */
+  {0x2b, 0x2b, 0x2b}  /* 9 */
+};
+
+#define COLOR(i) (&COLORS_TABLE[i])
+
+#define C_MESSAGE_TEXT COLOR(8)
+#define C_MESSAGE_BOX  COLOR(9)
+
+typedef struct {
+  PangoLayout *layout;
+
+  uint_t w, h;
+} text_t;
+
+typedef struct {
+  unsigned char *data;
+  size_t size, allocated;
+} buffer_t;
+
+#define FLAG_BOLD    (1 << 0)
+#define FLAG_STRIKE  (1 << 1)
+#define FLAG_ITALIC  (1 << 2)
+#define FLAG_TT      (1 << 3)
+
+typedef struct _node_t {
+  uint_t flags;
+  buffer_t *buffer;
+
+  struct _node_t *next;
+} node_t;
+
+void *malloc_protected(size_t z) {
+  void *p;
+
+  if (!(p = malloc(z))) {
+    fputs("out of memory.\n", stderr);
+
+    exit(1);
+  }
+
+  return p;
+}
+
+void *realloc_protected(void *old_p, size_t z) {
+  void *p;
+
+  if (!(p = realloc(old_p, z))) {
+    fputs("out of memory.\n", stderr);
+
+    exit(1);
+  }
+
+  return p;
+}
+
+buffer_t *new_buffer() {
+  buffer_t *buffer;
+
+  buffer = malloc_protected(sizeof(buffer_t));
+ 
+  buffer->data = NULL;
+  buffer->size = 0;
+  buffer->allocated = 0;
+
+  return buffer;
+}
+
+void free_buffer(buffer_t *buffer) {
+  free(buffer->data);
+  free(buffer);
+}
+
+void buffer_expand(buffer_t *buffer, size_t size) {
+  buffer->allocated += size;
+
+  buffer->data = realloc_protected(buffer->data, buffer->allocated);
+}
+
+void buffer_append_data(buffer_t *buffer, unsigned char *data, size_t data_length) {
+  size_t tail = buffer->size;
+ 
+  buffer->size += data_length;
+
+  if (buffer->size > buffer->allocated)
+    buffer_expand(buffer, data_length);
+
+  for (size_t i = 0; i < data_length; i++)
+    buffer->data[tail + i] = data[i];
+}
+
+#define BUFFER_APPEND(b, s) buffer_append_data(b, s, strlen(s))
+#define BUFFER_CONCAT(b1, b2) buffer_append_data(b1, (b2)->data, (b2)->size);
+
+node_t *new_node() {
+  node_t *node;
+
+  node = malloc_protected(sizeof(node_t));
+
+  node->flags = 0;
+  node->buffer = new_buffer();
+  node->next = NULL;
+
+  return node;
+}
+
+void free_node(node_t *node) {
+  if (node->next)
+    free_node(node->next);
+
+  free_buffer(node->buffer);
+  free(node);
+}
+
+#define SET(flag) {\
+  if (tail->flags & FLAG_##flag) {\
+    tail = tail->next = new_node();\
+    continue;\
+  }\
+  uint_t flags = tail->flags;\
+  tail = tail->next = new_node();\
+  tail->flags = flags | FLAG_##flag;\
+}
+
+void preprocess_text(buffer_t *buffer, char *text) {
+  uint_t state;
+  node_t *head;
+  node_t *tail;
+
+  head = tail = new_node();
+
+  for (size_t i = 0; i < strlen(text); i++) {
+    unsigned char c, nc;
+
+    c = text[i];
+    nc = text[i+1];
+
+    if (c == '`') {
+      SET(TT);
+    } else if (c == '*' && nc == '*') {
+      i++;
+
+      SET(BOLD);
+    } else if (c == '~' && nc == '~') {
+      i++;
+
+      SET(STRIKE);
+    } else if (c == '_' && nc == '_') {
+      i++;
+
+      SET(ITALIC);
+    } else {
+      buffer_append_data(tail->buffer, &c, 1);
+    }
+  }
+
+  if (tail->flags) {
+    tail->flags = 0;
+  }
+
+  node_t *start = head;
+
+  while (head) {
+    if (head->flags & FLAG_BOLD)
+      BUFFER_APPEND(buffer, "<b>");
+    if (head->flags & FLAG_STRIKE)
+      BUFFER_APPEND(buffer, "<s>");
+    if (head->flags & FLAG_ITALIC)
+      BUFFER_APPEND(buffer, "<i>");
+    if (head->flags & FLAG_TT)
+      BUFFER_APPEND(buffer, "<tt>");
+
+    //printf("%d %d %d\n", head->flags & FLAG_BOLD, head->flags & FLAG_STRIKE, head->flags & FLAG_ITALIC);
+
+    BUFFER_CONCAT(buffer, head->buffer);
+
+    if (head->flags & FLAG_TT)
+      BUFFER_APPEND(buffer, "</tt>");
+    if (head->flags & FLAG_ITALIC)
+      BUFFER_APPEND(buffer, "</i>");
+    if (head->flags & FLAG_STRIKE)
+      BUFFER_APPEND(buffer, "</s>");
+    if (head->flags & FLAG_BOLD)
+      BUFFER_APPEND(buffer, "</b>");
+
+    head = head->next;
+  }
+
+  free_node(start);
+}
+
+text_t *new_text(cairo_t *cr, char *s, int size, int weight, int preprocess) {
+  text_t *text;
+  PangoFontDescription *font_description;
+  buffer_t *buffer;
+
+  text = malloc_protected(sizeof(text_t));
+
+  font_description = pango_font_description_from_string("Apple Color Emoji 16,Sans 16");
+  pango_font_description_set_absolute_size(font_description, size * PANGO_SCALE);
+  pango_font_description_set_weight(font_description, weight);
+
+  text->layout = pango_cairo_create_layout(cr);
+  pango_layout_set_font_description(text->layout, font_description);
+  pango_layout_set_wrap(text->layout, PANGO_WRAP_WORD);
+  pango_layout_set_width(text->layout, WRAP_WIDTH * PANGO_SCALE);
+
+  pango_font_description_free(font_description);
+
+  if (preprocess) {
+    buffer = new_buffer();
+    preprocess_text(buffer, s);
+
+    pango_layout_set_markup(text->layout, buffer->data, buffer->size);
+
+    free_buffer(buffer);
+  } else
+    pango_layout_set_text(text->layout, s, -1);
+
+  pango_layout_get_pixel_size(text->layout, &text->w, &text->h);
+
+  return text;
+}
+
+void free_text(text_t *text) {
+  g_object_unref(text->layout);
+
+  free(text);
+}
+
+/* TO-DO: do this better */
+void get_text_size(char *s, uint_t size, uint_t weight, uint_t *w, uint_t *h, int preprocess) {
+  cairo_surface_t *surface;
+  cairo_t *cr;
+
+  surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1);
+  cr = cairo_create(surface);
+
+  text_t *text = new_text(cr, s, size, weight, preprocess);
+
+  *w = text->w;
+  *h = text->h;
+
+  free_text(text);
+  cairo_destroy(cr);
+  cairo_surface_destroy(surface);
+}
+
+void select_color(cairo_t *cr, color_t *color) {
+  cairo_set_source_rgb(cr, color->r / 255.0F, color->g / 255.0F, color->b / 255.0F);
+}
+
+void paste_clipped_image(cairo_t *cr, uint_t x, uint_t y, char *path) {
+  cairo_surface_t *surface, *surface2;
+  cairo_t *ic;
+
+  surface = cairo_image_surface_create_from_png(path);
+  ic = cairo_create(surface);
+
+  cairo_scale(ic, ((double)AVATAR_DIM) / cairo_image_surface_get_width(surface), ((double)AVATAR_DIM) / cairo_image_surface_get_height(surface));
+  cairo_set_source_surface(ic, surface, 0, 0);
+  cairo_paint(ic);
+
+  cairo_destroy(ic);
+
+  surface2 = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, AVATAR_DIM, AVATAR_DIM);
+  ic = cairo_create(surface2);
+
+  cairo_arc(ic, AVATAR_DIM/2, AVATAR_DIM/2, AVATAR_DIM/2, 0, 2*M_PI);
+  cairo_clip(ic);
+  cairo_new_path(ic); 
+
+  cairo_set_source_surface(ic, surface, 0, 0);
+  cairo_paint(ic);
+
+  cairo_set_source_surface(cr, surface2, x, y);
+  cairo_paint(cr);
+
+  cairo_destroy(ic);
+  cairo_surface_destroy(surface);
+  cairo_surface_destroy(surface2); 
+}
+
+void rounded_rectangle(cairo_t *cr, uint_t x, uint_t y, uint_t w, uint_t h, uint_t r) {
+  cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
+
+  cairo_new_sub_path(cr);
+  cairo_arc(cr, x + r, y + r, r, M_PI, 3 * M_PI / 2);
+  cairo_arc(cr, x + w - r, y + r, r, 3 *M_PI / 2, 2 * M_PI);
+  cairo_arc(cr, x + w - r, y + h - r, r, 0, M_PI / 2);
+  cairo_arc(cr, x + r, y + h - r, r, M_PI / 2, M_PI);
+  cairo_close_path(cr);
+
+  cairo_fill(cr);
+}
+
+void text(cairo_t *cr, uint_t x, uint_t y, text_t *text) {
+  cairo_move_to(cr, x, y);
+  pango_cairo_update_layout(cr, text->layout);
+  pango_cairo_show_layout(cr, text->layout);
+}
+
+void paste_image(cairo_t *cr, uint_t x, uint_t y, char *path) {
+  cairo_surface_t *surface;
+
+  surface = cairo_image_surface_create_from_png(path);
+  cairo_set_source_surface(cr, surface, x, y);
+  cairo_paint(cr);
+
+  cairo_surface_destroy(surface);
+}
+
+void fit(cairo_surface_t **surface, uint_t width, uint_t height) {
+  uint_t image_width, image_height;
+  uint_t new_width, new_height;
+  double image_ratio, new_ratio;
+
+  cairo_surface_t *surface2;
+  cairo_t *cr;
+
+  image_width = cairo_image_surface_get_width(*surface);
+  image_height = cairo_image_surface_get_height(*surface);
+
+  if (image_width <= width && image_height <= height)
+    return;
+
+  new_width = width;
+  new_height = height;
+
+  image_ratio = ((double)image_width) / ((double)image_height);
+  new_ratio = ((double)width) / ((double)height);
+
+  if (image_ratio > new_ratio)
+    new_height = ((double)image_height) / ((double)image_width) * ((double)width);
+  else
+    new_width = ((double)image_width) / ((double)image_height) * ((double)height);
+
+  surface2 = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, new_width, new_height);
+  cr = cairo_create(surface2);
+
+  cairo_scale(cr, ((double)new_width) / ((double)image_width), ((double)new_height) / ((double)image_height));
+  cairo_set_source_surface(cr, *surface, 0, 0);
+  cairo_paint(cr);
+
+  cairo_destroy(cr);
+  cairo_surface_destroy(*surface);
+
+  *surface = surface2;
+}
+
+void read_uint(uint_t *r) {
+  *r = 0;
+
+  for (uint_t i = 0; i < sizeof(uint_t); i++)
+    *r |= ((uint_t)fgetc(stdin)) << (i*8);
+}
+
+void read_string(buffer_t *buffer) {
+  uint_t length;
+
+  read_uint(&length);
+  buffer_expand(buffer, length);
+
+  buffer->size += fread(buffer->data + buffer->size, 1, length, stdin);
+
+  buffer_append_data(buffer, &"\0", 1);
+}
+
+main() {
+  uint_t avatar_width;
+  uint_t image_width;
+  uint_t image_height;
+
+  uint_t x, y;
+  uint_t w, h;
+
+  cairo_surface_t *surface;
+  cairo_t *cr;
+
+  text_t *nickname; 
+  text_t *message;
+  
+  buffer_t *output_path;
+  buffer_t *avatar_path;
+  buffer_t *username_text;
+  uint_t username_color;
+  buffer_t *message_text;
+  
+  uint_t nickname_w, nickname_h;
+  uint_t message_w, message_h;
+
+  output_path = new_buffer();
+  avatar_path = new_buffer();
+  username_text = new_buffer();
+  message_text = new_buffer();
+
+  read_string(output_path);
+  read_string(avatar_path);
+  read_string(username_text);
+  read_uint(&username_color);
+  read_string(message_text);
+
+  username_color = MIN(username_color, 6);
+
+  get_text_size(username_text->data, NICKNAME_TEXT_SIZE, PANGO_WEIGHT_BOLD, &nickname_w, &nickname_h, 0);
+  get_text_size(message_text->data, MESSAGE_TEXT_SIZE, PANGO_WEIGHT_NORMAL, &message_w, &message_h, 1);
+
+  image_width = AVATAR_WIDTH + MAX(message_w, nickname_w) + HPADDING*2;
+  image_height = message_h + nickname_h + VPADDING*2;
+
+  if (image_width - AVATAR_WIDTH < MESSAGE_BOX_MIN)
+    image_width = AVATAR_WIDTH + MESSAGE_BOX_MIN;
+
+  surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, image_width, image_height+AVATAR_OFFSET);
+  cr = cairo_create(surface);
+
+  nickname = new_text(cr, username_text->data, NICKNAME_TEXT_SIZE, PANGO_WEIGHT_BOLD, 0);
+  message = new_text(cr, message_text->data, MESSAGE_TEXT_SIZE, PANGO_WEIGHT_NORMAL, 1); 
+
+  free_buffer(output_path);
+  free_buffer(avatar_path);
+  free_buffer(username_text);
+  free_buffer(message_text);
+
+  x = 0;
+  y = image_height - AVATAR_DIM + AVATAR_OFFSET;
+
+  paste_clipped_image(cr, x, y, avatar_path->data);
+
+  x = AVATAR_WIDTH;
+  y = 0;
+  w = MAX(message->w, nickname->w) + HPADDING*2;
+  h = message->h + nickname->h + VPADDING*2;
+
+  if (w < MESSAGE_BOX_MIN)
+    w = MESSAGE_BOX_MIN;
+
+  select_color(cr, C_MESSAGE_BOX);
+  rounded_rectangle(cr, x, y, w, h, MESSAGE_BOX_RADIUS);
+
+  x = AVATAR_WIDTH - TAIL_WIDTH + 9;
+  y = image_height - TAIL_HEIGHT;
+
+  paste_image(cr, x, y, TAIL_PATH);
+
+  x = AVATAR_WIDTH + HPADDING;
+  y = VPADDING / 2;
+
+  select_color(cr, COLOR(username_color));
+  text(cr, x, y, nickname);
+
+  x = AVATAR_WIDTH + HPADDING;
+  y = nickname->h + VPADDING - 3;
+
+  select_color(cr, C_MESSAGE_TEXT);
+  text(cr, x, y, message);
+
+  cairo_destroy(cr);
+
+  fit(&surface, 512, 512);
+  cairo_surface_write_to_png(surface, output_path->data);
+
+  cairo_surface_destroy(surface);
+
+  free_text(nickname);
+  free_text(message);
+}

+ 3 - 0
makeshot/mk.sh

@@ -0,0 +1,3 @@
+#!/bin/bash
+
+gcc -w -Os `pkg-config --cflags pango` makeshot.c -o makeshot -lcairo -lpangocairo-1.0 -lpango-1.0 -lgobject-2.0

+ 0 - 5
models.py

@@ -12,11 +12,6 @@ class Gif(Model):
   action = ForeignKeyField('models.Action', related_name='gifs')
   file_id = CharField(max_length=64, unique=True)
 
-class UserColor(Model):
-  id = IntField(pk=True)
-  user_id = IntField(unique=True)
-  color = IntField()
-
 class StickerPack(Model):
   id = IntField(pk=True)
   short_name = CharField(max_length=256, unique=True)