gvdevice_kitty.c 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. /*************************************************************************
  2. * Copyright (c) 2011 AT&T Intellectual Property
  3. * All rights reserved. This program and the accompanying materials
  4. * are made available under the terms of the Eclipse Public License v1.0
  5. * which accompanies this distribution, and is available at
  6. * https://www.eclipse.org/legal/epl-v10.html
  7. *
  8. * Contributors: Details at https://graphviz.org
  9. *************************************************************************/
  10. #include "config.h"
  11. #include <assert.h>
  12. #include <stdbool.h>
  13. #include <stdio.h>
  14. #include <stdlib.h>
  15. #include <common/types.h>
  16. #include <gvc/gvio.h>
  17. #include <gvc/gvplugin_device.h>
  18. #include <util/alloc.h>
  19. #ifdef HAVE_LIBZ
  20. #include <zlib.h>
  21. #endif
  22. static void fix_colors(unsigned char *imagedata, size_t imagedata_size) {
  23. for (size_t i = 0; i < imagedata_size; i += 4) {
  24. const unsigned char blue = imagedata[i];
  25. const unsigned char red = imagedata[i + 2];
  26. imagedata[i] = red;
  27. imagedata[i + 2] = blue;
  28. }
  29. }
  30. static size_t div_up(size_t dividend, size_t divisor) {
  31. return dividend / divisor + (dividend % divisor != 0);
  32. }
  33. static const char base64_alphabet[] =
  34. "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
  35. static size_t base64_encoded_size(size_t original_size) {
  36. return div_up(original_size, 3) * 4;
  37. }
  38. static char *base64_encode(const unsigned char *data, size_t size) {
  39. size_t buf_i = 0;
  40. size_t data_i = 0;
  41. char *buf = gv_alloc(base64_encoded_size(size));
  42. while (data_i < size) {
  43. int v;
  44. unsigned char d0 = data[data_i];
  45. v = (d0 & 0xFC) >> 2; // 1111_1100
  46. buf[buf_i++] = base64_alphabet[v];
  47. unsigned char d1 = data_i + 1 < size ? data[data_i + 1] : 0;
  48. v = (d0 & 0x03) << 4 // 0000_0011
  49. | (d1 & 0xF0) >> 4; // 1111_0000
  50. buf[buf_i++] = base64_alphabet[v];
  51. if (size <= data_i + 1) {
  52. goto end;
  53. }
  54. unsigned char d2 = data_i + 2 < size ? data[data_i + 2] : 0;
  55. v = (d1 & 0x0F) << 2 // 0000_1111
  56. | (d2 & 0xC0) >> 6; // 1100_0000
  57. buf[buf_i++] = base64_alphabet[v];
  58. if (size <= data_i + 2) {
  59. goto end;
  60. }
  61. v = d2 & 0x3F; // 0011_1111
  62. buf[buf_i++] = base64_alphabet[v];
  63. data_i += 3;
  64. }
  65. end:
  66. while (buf_i % 4 != 0) {
  67. buf[buf_i++] = base64_alphabet[64];
  68. }
  69. return buf;
  70. }
  71. static void kitty_write(unsigned char *data, size_t data_size, unsigned width,
  72. unsigned height, bool is_compressed) {
  73. const size_t chunk_size = 4096;
  74. char *output = base64_encode(data, data_size);
  75. size_t offset = 0;
  76. size_t size = base64_encoded_size(data_size);
  77. while (offset < size) {
  78. int has_next_chunk = offset + chunk_size <= size;
  79. if (offset == 0) {
  80. printf("\033_Ga=T,f=32,s=%u,v=%u%s%s;", width, height,
  81. chunk_size < size ? ",m=1" : "", is_compressed ? ",o=z" : "");
  82. } else {
  83. printf("\033_Gm=%d;", has_next_chunk);
  84. }
  85. size_t this_chunk_size = has_next_chunk ? chunk_size : size - offset;
  86. fwrite(output + offset, this_chunk_size, 1, stdout);
  87. printf("\033\\");
  88. offset += chunk_size;
  89. }
  90. printf("\n");
  91. free(output);
  92. }
  93. static void kitty_format(GVJ_t *job) {
  94. unsigned char *imagedata = (unsigned char *)job->imagedata;
  95. size_t imagedata_size = job->width * job->height * 4;
  96. fix_colors(imagedata, imagedata_size);
  97. kitty_write(imagedata, imagedata_size, job->width, job->height, false);
  98. }
  99. static gvdevice_features_t device_features_kitty = {
  100. GVDEVICE_DOES_TRUECOLOR, /* flags */
  101. {0., 0.}, /* default margin - points */
  102. {0., 0.}, /* default page width, height - points */
  103. {96., 96.}, /* dpi */
  104. };
  105. static gvdevice_engine_t device_engine_kitty = {.format = kitty_format};
  106. #ifdef HAVE_LIBZ
  107. static int zlib_compress(unsigned char *source, uLong source_len,
  108. unsigned char **dest, size_t *dest_len) {
  109. uLong dest_cap = compressBound(source_len);
  110. *dest = gv_alloc(dest_cap);
  111. const int ret = compress(*dest, &dest_cap, source, source_len);
  112. *dest_len = dest_cap;
  113. return ret;
  114. }
  115. static void zkitty_format(GVJ_t *job) {
  116. unsigned char *imagedata = (unsigned char *)job->imagedata;
  117. const uLong imagedata_size = job->width * job->height * 4;
  118. fix_colors(imagedata, imagedata_size);
  119. unsigned char *zbuf;
  120. size_t zsize;
  121. int ret = zlib_compress(imagedata, imagedata_size, &zbuf, &zsize);
  122. assert(ret == Z_OK);
  123. (void)ret;
  124. kitty_write(zbuf, zsize, job->width, job->height, true);
  125. free(zbuf);
  126. }
  127. static gvdevice_features_t device_features_zkitty = {
  128. GVDEVICE_DOES_TRUECOLOR, /* flags */
  129. {0., 0.}, /* default margin - points */
  130. {0., 0.}, /* default page width, height - points */
  131. {96., 96.}, /* dpi */
  132. };
  133. static gvdevice_engine_t device_engine_zkitty = {.format = zkitty_format};
  134. #endif
  135. gvplugin_installed_t gvdevice_types_kitty[] = {
  136. {0, "kitty:cairo", 0, &device_engine_kitty, &device_features_kitty},
  137. #ifdef HAVE_LIBZ
  138. {1, "kittyz:cairo", 1, &device_engine_zkitty, &device_features_zkitty},
  139. #endif
  140. {0, NULL, 0, NULL, NULL}};