makeshot.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516
  1. #include <math.h>
  2. #include <cairo/cairo.h>
  3. #include <pango/pangocairo.h>
  4. #define NICKNAME_TEXT_SIZE 23
  5. #define MESSAGE_TEXT_SIZE 24
  6. #define HPADDING 18
  7. #define VPADDING 8
  8. #define AVATAR_OFFSET 3
  9. #define AVATAR_PADDING 12
  10. #define AVATAR_DIM 75
  11. #define AVATAR_WIDTH (AVATAR_DIM + AVATAR_PADDING)
  12. #define TAIL_WIDTH 18
  13. #define TAIL_HEIGHT 30
  14. #define MESSAGE_BOX_RADIUS 8
  15. #define MESSAGE_BOX_MIN 160
  16. #define WRAP_WIDTH 400
  17. #define TAIL_PATH "./resources/tail.png"
  18. #define MAX(x, y) ((x) >= (y)? (x): (y))
  19. #define MIN(x, y) ((x) <= (y)? (x): (y))
  20. typedef unsigned int uint_t;
  21. typedef struct {
  22. int r, g, b;
  23. } color_t;
  24. color_t COLORS_TABLE[] = {
  25. /* Nickname colors */
  26. {0xee, 0x49, 0x28}, /* 0 */
  27. {0x41, 0xa9, 0x03}, /* 1 */
  28. {0xe0, 0x96, 0x02}, /* 2 */
  29. {0x0f, 0x94, 0xed}, /* 3 */
  30. {0x8f, 0x3b, 0xf7}, /* 4 */
  31. {0xfc, 0x43, 0x80}, /* 5 */
  32. {0x00, 0xa1, 0xc4}, /* 6 */
  33. {0xeb, 0x70, 0x02}, /* 7 */
  34. /* Message text color */
  35. {0xff, 0xff, 0xff}, /* 8 */
  36. /* Message box color */
  37. {0x2b, 0x2b, 0x2b} /* 9 */
  38. };
  39. #define COLOR(i) (&COLORS_TABLE[i])
  40. #define C_MESSAGE_TEXT COLOR(8)
  41. #define C_MESSAGE_BOX COLOR(9)
  42. typedef struct {
  43. PangoLayout *layout;
  44. uint_t w, h;
  45. } text_t;
  46. typedef struct {
  47. unsigned char *data;
  48. size_t size, allocated;
  49. } buffer_t;
  50. #define FLAG_BOLD (1 << 0)
  51. #define FLAG_STRIKE (1 << 1)
  52. #define FLAG_ITALIC (1 << 2)
  53. #define FLAG_TT (1 << 3)
  54. typedef struct _node_t {
  55. uint_t flags;
  56. buffer_t *buffer;
  57. struct _node_t *next;
  58. } node_t;
  59. void *malloc_protected(size_t z) {
  60. void *p;
  61. if (!(p = malloc(z))) {
  62. fputs("out of memory.\n", stderr);
  63. exit(1);
  64. }
  65. return p;
  66. }
  67. void *realloc_protected(void *old_p, size_t z) {
  68. void *p;
  69. if (!(p = realloc(old_p, z))) {
  70. fputs("out of memory.\n", stderr);
  71. exit(1);
  72. }
  73. return p;
  74. }
  75. buffer_t *new_buffer() {
  76. buffer_t *buffer;
  77. buffer = malloc_protected(sizeof(buffer_t));
  78. buffer->data = NULL;
  79. buffer->size = 0;
  80. buffer->allocated = 0;
  81. return buffer;
  82. }
  83. void free_buffer(buffer_t *buffer) {
  84. free(buffer->data);
  85. free(buffer);
  86. }
  87. void buffer_expand(buffer_t *buffer, size_t size) {
  88. buffer->allocated += size;
  89. buffer->data = realloc_protected(buffer->data, buffer->allocated);
  90. }
  91. void buffer_append_data(buffer_t *buffer, unsigned char *data, size_t data_length) {
  92. size_t tail = buffer->size;
  93. buffer->size += data_length;
  94. if (buffer->size > buffer->allocated)
  95. buffer_expand(buffer, data_length);
  96. for (size_t i = 0; i < data_length; i++)
  97. buffer->data[tail + i] = data[i];
  98. }
  99. #define BUFFER_APPEND(b, s) buffer_append_data(b, s, strlen(s))
  100. #define BUFFER_CONCAT(b1, b2) buffer_append_data(b1, (b2)->data, (b2)->size);
  101. node_t *new_node() {
  102. node_t *node;
  103. node = malloc_protected(sizeof(node_t));
  104. node->flags = 0;
  105. node->buffer = new_buffer();
  106. node->next = NULL;
  107. return node;
  108. }
  109. void free_node(node_t *node) {
  110. if (node->next)
  111. free_node(node->next);
  112. free_buffer(node->buffer);
  113. free(node);
  114. }
  115. #define SET(flag) {\
  116. if (tail->flags & FLAG_##flag) {\
  117. tail = tail->next = new_node();\
  118. continue;\
  119. }\
  120. uint_t flags = tail->flags;\
  121. tail = tail->next = new_node();\
  122. tail->flags = flags | FLAG_##flag;\
  123. }
  124. void preprocess_text(buffer_t *buffer, char *text) {
  125. uint_t state;
  126. node_t *head;
  127. node_t *tail;
  128. head = tail = new_node();
  129. for (size_t i = 0; i < strlen(text); i++) {
  130. unsigned char c, nc;
  131. c = text[i];
  132. nc = text[i+1];
  133. if (c == '`') {
  134. SET(TT);
  135. } else if (c == '*' && nc == '*') {
  136. i++;
  137. SET(BOLD);
  138. } else if (c == '~' && nc == '~') {
  139. i++;
  140. SET(STRIKE);
  141. } else if (c == '_' && nc == '_') {
  142. i++;
  143. SET(ITALIC);
  144. } else {
  145. buffer_append_data(tail->buffer, &c, 1);
  146. }
  147. }
  148. if (tail->flags) {
  149. tail->flags = 0;
  150. }
  151. node_t *start = head;
  152. while (head) {
  153. if (head->flags & FLAG_BOLD)
  154. BUFFER_APPEND(buffer, "<b>");
  155. if (head->flags & FLAG_STRIKE)
  156. BUFFER_APPEND(buffer, "<s>");
  157. if (head->flags & FLAG_ITALIC)
  158. BUFFER_APPEND(buffer, "<i>");
  159. if (head->flags & FLAG_TT)
  160. BUFFER_APPEND(buffer, "<tt>");
  161. //printf("%d %d %d\n", head->flags & FLAG_BOLD, head->flags & FLAG_STRIKE, head->flags & FLAG_ITALIC);
  162. BUFFER_CONCAT(buffer, head->buffer);
  163. if (head->flags & FLAG_TT)
  164. BUFFER_APPEND(buffer, "</tt>");
  165. if (head->flags & FLAG_ITALIC)
  166. BUFFER_APPEND(buffer, "</i>");
  167. if (head->flags & FLAG_STRIKE)
  168. BUFFER_APPEND(buffer, "</s>");
  169. if (head->flags & FLAG_BOLD)
  170. BUFFER_APPEND(buffer, "</b>");
  171. head = head->next;
  172. }
  173. free_node(start);
  174. }
  175. text_t *new_text(cairo_t *cr, char *s, int size, int weight, int preprocess) {
  176. text_t *text;
  177. PangoFontDescription *font_description;
  178. buffer_t *buffer;
  179. text = malloc_protected(sizeof(text_t));
  180. font_description = pango_font_description_from_string("Apple Color Emoji 16,Sans 16");
  181. pango_font_description_set_absolute_size(font_description, size * PANGO_SCALE);
  182. pango_font_description_set_weight(font_description, weight);
  183. text->layout = pango_cairo_create_layout(cr);
  184. pango_layout_set_font_description(text->layout, font_description);
  185. pango_layout_set_wrap(text->layout, PANGO_WRAP_WORD);
  186. pango_layout_set_width(text->layout, WRAP_WIDTH * PANGO_SCALE);
  187. pango_font_description_free(font_description);
  188. if (preprocess) {
  189. buffer = new_buffer();
  190. preprocess_text(buffer, s);
  191. pango_layout_set_markup(text->layout, buffer->data, buffer->size);
  192. free_buffer(buffer);
  193. } else
  194. pango_layout_set_text(text->layout, s, -1);
  195. pango_layout_get_pixel_size(text->layout, &text->w, &text->h);
  196. return text;
  197. }
  198. void free_text(text_t *text) {
  199. g_object_unref(text->layout);
  200. free(text);
  201. }
  202. /* TO-DO: do this better */
  203. void get_text_size(char *s, uint_t size, uint_t weight, uint_t *w, uint_t *h, int preprocess) {
  204. cairo_surface_t *surface;
  205. cairo_t *cr;
  206. surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1);
  207. cr = cairo_create(surface);
  208. text_t *text = new_text(cr, s, size, weight, preprocess);
  209. *w = text->w;
  210. *h = text->h;
  211. free_text(text);
  212. cairo_destroy(cr);
  213. cairo_surface_destroy(surface);
  214. }
  215. void select_color(cairo_t *cr, color_t *color) {
  216. cairo_set_source_rgb(cr, color->r / 255.0F, color->g / 255.0F, color->b / 255.0F);
  217. }
  218. void paste_clipped_image(cairo_t *cr, uint_t x, uint_t y, char *path) {
  219. cairo_surface_t *surface, *surface2;
  220. cairo_t *ic;
  221. surface = cairo_image_surface_create_from_png(path);
  222. ic = cairo_create(surface);
  223. cairo_scale(ic, ((double)AVATAR_DIM) / cairo_image_surface_get_width(surface), ((double)AVATAR_DIM) / cairo_image_surface_get_height(surface));
  224. cairo_set_source_surface(ic, surface, 0, 0);
  225. cairo_paint(ic);
  226. cairo_destroy(ic);
  227. surface2 = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, AVATAR_DIM, AVATAR_DIM);
  228. ic = cairo_create(surface2);
  229. cairo_arc(ic, AVATAR_DIM/2, AVATAR_DIM/2, AVATAR_DIM/2, 0, 2*M_PI);
  230. cairo_clip(ic);
  231. cairo_new_path(ic);
  232. cairo_set_source_surface(ic, surface, 0, 0);
  233. cairo_paint(ic);
  234. cairo_set_source_surface(cr, surface2, x, y);
  235. cairo_paint(cr);
  236. cairo_destroy(ic);
  237. cairo_surface_destroy(surface);
  238. cairo_surface_destroy(surface2);
  239. }
  240. void rounded_rectangle(cairo_t *cr, uint_t x, uint_t y, uint_t w, uint_t h, uint_t r) {
  241. cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
  242. cairo_new_sub_path(cr);
  243. cairo_arc(cr, x + r, y + r, r, M_PI, 3 * M_PI / 2);
  244. cairo_arc(cr, x + w - r, y + r, r, 3 *M_PI / 2, 2 * M_PI);
  245. cairo_arc(cr, x + w - r, y + h - r, r, 0, M_PI / 2);
  246. cairo_arc(cr, x + r, y + h - r, r, M_PI / 2, M_PI);
  247. cairo_close_path(cr);
  248. cairo_fill(cr);
  249. }
  250. void text(cairo_t *cr, uint_t x, uint_t y, text_t *text) {
  251. cairo_move_to(cr, x, y);
  252. pango_cairo_update_layout(cr, text->layout);
  253. pango_cairo_show_layout(cr, text->layout);
  254. }
  255. void paste_image(cairo_t *cr, uint_t x, uint_t y, char *path) {
  256. cairo_surface_t *surface;
  257. surface = cairo_image_surface_create_from_png(path);
  258. cairo_set_source_surface(cr, surface, x, y);
  259. cairo_paint(cr);
  260. cairo_surface_destroy(surface);
  261. }
  262. void fit(cairo_surface_t **surface, uint_t width, uint_t height) {
  263. uint_t image_width, image_height;
  264. uint_t new_width, new_height;
  265. double image_ratio, new_ratio;
  266. cairo_surface_t *surface2;
  267. cairo_t *cr;
  268. image_width = cairo_image_surface_get_width(*surface);
  269. image_height = cairo_image_surface_get_height(*surface);
  270. if (image_width <= width && image_height <= height)
  271. return;
  272. new_width = width;
  273. new_height = height;
  274. image_ratio = ((double)image_width) / ((double)image_height);
  275. new_ratio = ((double)width) / ((double)height);
  276. if (image_ratio > new_ratio)
  277. new_height = ((double)image_height) / ((double)image_width) * ((double)width);
  278. else
  279. new_width = ((double)image_width) / ((double)image_height) * ((double)height);
  280. surface2 = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, new_width, new_height);
  281. cr = cairo_create(surface2);
  282. cairo_scale(cr, ((double)new_width) / ((double)image_width), ((double)new_height) / ((double)image_height));
  283. cairo_set_source_surface(cr, *surface, 0, 0);
  284. cairo_paint(cr);
  285. cairo_destroy(cr);
  286. cairo_surface_destroy(*surface);
  287. *surface = surface2;
  288. }
  289. void read_uint(uint_t *r) {
  290. *r = 0;
  291. for (uint_t i = 0; i < sizeof(uint_t); i++)
  292. *r |= ((uint_t)fgetc(stdin)) << (i*8);
  293. }
  294. void read_string(buffer_t *buffer) {
  295. uint_t length;
  296. read_uint(&length);
  297. buffer_expand(buffer, length);
  298. buffer->size += fread(buffer->data + buffer->size, 1, length, stdin);
  299. buffer_append_data(buffer, &"\0", 1);
  300. }
  301. main() {
  302. uint_t avatar_width;
  303. uint_t image_width;
  304. uint_t image_height;
  305. uint_t x, y;
  306. uint_t w, h;
  307. cairo_surface_t *surface;
  308. cairo_t *cr;
  309. text_t *nickname;
  310. text_t *message;
  311. buffer_t *output_path;
  312. buffer_t *avatar_path;
  313. buffer_t *username_text;
  314. uint_t username_color;
  315. buffer_t *message_text;
  316. uint_t nickname_w, nickname_h;
  317. uint_t message_w, message_h;
  318. output_path = new_buffer();
  319. avatar_path = new_buffer();
  320. username_text = new_buffer();
  321. message_text = new_buffer();
  322. read_string(output_path);
  323. read_string(avatar_path);
  324. read_string(username_text);
  325. read_uint(&username_color);
  326. read_string(message_text);
  327. username_color = MIN(username_color, 6);
  328. get_text_size(username_text->data, NICKNAME_TEXT_SIZE, PANGO_WEIGHT_BOLD, &nickname_w, &nickname_h, 0);
  329. get_text_size(message_text->data, MESSAGE_TEXT_SIZE, PANGO_WEIGHT_NORMAL, &message_w, &message_h, 1);
  330. image_width = AVATAR_WIDTH + MAX(message_w, nickname_w) + HPADDING*2;
  331. image_height = message_h + nickname_h + VPADDING*2;
  332. if (image_width - AVATAR_WIDTH < MESSAGE_BOX_MIN)
  333. image_width = AVATAR_WIDTH + MESSAGE_BOX_MIN;
  334. surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, image_width, image_height+AVATAR_OFFSET);
  335. cr = cairo_create(surface);
  336. nickname = new_text(cr, username_text->data, NICKNAME_TEXT_SIZE, PANGO_WEIGHT_BOLD, 0);
  337. message = new_text(cr, message_text->data, MESSAGE_TEXT_SIZE, PANGO_WEIGHT_NORMAL, 1);
  338. free_buffer(output_path);
  339. free_buffer(avatar_path);
  340. free_buffer(username_text);
  341. free_buffer(message_text);
  342. x = 0;
  343. y = image_height - AVATAR_DIM + AVATAR_OFFSET;
  344. paste_clipped_image(cr, x, y, avatar_path->data);
  345. x = AVATAR_WIDTH;
  346. y = 0;
  347. w = MAX(message->w, nickname->w) + HPADDING*2;
  348. h = message->h + nickname->h + VPADDING*2;
  349. if (w < MESSAGE_BOX_MIN)
  350. w = MESSAGE_BOX_MIN;
  351. select_color(cr, C_MESSAGE_BOX);
  352. rounded_rectangle(cr, x, y, w, h, MESSAGE_BOX_RADIUS);
  353. x = AVATAR_WIDTH - TAIL_WIDTH + 9;
  354. y = image_height - TAIL_HEIGHT;
  355. paste_image(cr, x, y, TAIL_PATH);
  356. x = AVATAR_WIDTH + HPADDING;
  357. y = VPADDING / 2;
  358. select_color(cr, COLOR(username_color));
  359. text(cr, x, y, nickname);
  360. x = AVATAR_WIDTH + HPADDING;
  361. y = nickname->h + VPADDING - 3;
  362. select_color(cr, C_MESSAGE_TEXT);
  363. text(cr, x, y, message);
  364. cairo_destroy(cr);
  365. fit(&surface, 512, 512);
  366. cairo_surface_write_to_png(surface, output_path->data);
  367. cairo_surface_destroy(surface);
  368. free_text(nickname);
  369. free_text(message);
  370. }