Graphviz 13.0.0~dev.20250121.0651
Loading...
Searching...
No Matches
gvplugin_vt.c
Go to the documentation of this file.
1
3
4#include <assert.h>
5#include <gvc/gvplugin.h>
7#include <limits.h>
8#include <stddef.h>
9#include <util/gv_math.h>
10
11#include <gvc/gvio.h>
12
14typedef struct {
15 unsigned value;
16 unsigned red;
17 unsigned green;
18 unsigned blue;
19
20} color_t;
21
23static const color_t COLORS[] = {
24 {0, 0x00, 0x00, 0x00},
25 {1, 0xff, 0x00, 0x00},
26 {2, 0x00, 0xff, 0x00},
27 {3, 0xff, 0xff, 0x00},
28 {4, 0x00, 0x00, 0xff},
29 {5, 0xff, 0x00, 0xff},
30 {6, 0x00, 0xff, 0xff},
31 {7, 0xff, 0xff, 0xff},
32};
33
35static unsigned distance(const color_t base, unsigned red, unsigned green,
36 unsigned blue) {
37 unsigned diff = 0;
38 diff += red > base.red ? red - base.red : base.red - red;
39 diff += green > base.green ? green - base.green : base.green - green;
40 diff += blue > base.blue ? blue - base.blue : base.blue - blue;
41 return diff;
42}
43
45static unsigned get_color(unsigned red, unsigned green, unsigned blue) {
46 unsigned winner = 0;
47 unsigned diff = UINT_MAX;
48 for (size_t i = 0; i < sizeof(COLORS) / sizeof(COLORS[0]); ++i) {
49 unsigned d = distance(COLORS[i], red, green, blue);
50 if (d < diff) {
51 diff = d;
52 winner = COLORS[i].value;
53 }
54 }
55 return winner;
56}
57
58static void process(GVJ_t *job, int color_depth) {
59
60 unsigned char *data = job->imagedata;
61
62 assert(color_depth == 3 || color_depth == 24);
63
64 for (unsigned y = 0; y < job->height; y += 2) {
65 for (unsigned x = 0; x < job->width; ++x) {
66
67 {
68 // extract the upper pixel
69 unsigned offset =
70 y * job->width * BYTES_PER_PIXEL + x * BYTES_PER_PIXEL;
71 unsigned red = data[offset + 2];
72 unsigned green = data[offset + 1];
73 unsigned blue = data[offset];
74
75 // use this to select a foreground color
76 if (color_depth == 3) {
77 unsigned fg = get_color(red, green, blue);
78 gvprintf(job, "\033[3%um", fg);
79 } else {
80 assert(color_depth == 24);
81 gvprintf(job, "\033[38;2;%u;%u;%um", red, green, blue);
82 }
83 }
84
85 {
86 // extract the lower pixel
87 unsigned red = 0;
88 unsigned green = 0;
89 unsigned blue = 0;
90 if (y + 1 < job->height) {
91 unsigned offset =
92 (y + 1) * job->width * BYTES_PER_PIXEL + x * BYTES_PER_PIXEL;
93 red = data[offset + 2];
94 green = data[offset + 1];
95 blue = data[offset];
96 }
97
98 // use this to select a background color
99 if (color_depth == 3) {
100 unsigned bg = get_color(red, green, blue);
101 gvprintf(job, "\033[4%um", bg);
102 } else {
103 assert(color_depth == 24);
104 gvprintf(job, "\033[48;2;%u;%u;%um", red, green, blue);
105 }
106 }
107
108 // print unicode “upper half block” to effectively do two rows of
109 // pixels per one terminal row
110 gvprintf(job, "▀\033[0m");
111 }
112 gvprintf(job, "\n");
113 }
114}
115
116static void process3(GVJ_t *job) { process(job, 3); }
117
118static void process24(GVJ_t *job) { process(job, 24); }
119
121static unsigned rgb_to_grayscale(unsigned red, unsigned green, unsigned blue) {
122
125
126 const double r_linear = red / 255.0;
127 const double g_linear = green / 255.0;
128 const double b_linear = blue / 255.0;
129
130 const double y_linear =
131 0.2126 * r_linear + 0.7152 * g_linear + 0.0722 * b_linear;
132 return (unsigned)(y_linear * 255.999);
133}
134
141static void processNup(GVJ_t *job, unsigned y_stride, unsigned x_stride,
142 const char **tiles) {
143 assert(y_stride > 0);
144 assert(x_stride > 0);
145 assert(tiles != NULL);
146 for (unsigned i = 0; i < y_stride; ++i) {
147 for (unsigned j = 0; j < x_stride; ++j) {
148 assert(tiles[i * x_stride + j] != NULL && "missing or not enough tiles");
149 }
150 }
151
152 unsigned char *data = job->imagedata;
153
154 for (unsigned y = 0; y < job->height; y += y_stride) {
155 for (unsigned x = 0; x < job->width; x += x_stride) {
156
157 unsigned index = 0;
158
159 for (unsigned y_offset = 0;
160 y + y_offset < job->height && y_offset < y_stride; ++y_offset) {
161 for (unsigned x_offset = 0;
162 x + x_offset < job->width && x_offset < x_stride; ++x_offset) {
163
164 const unsigned offset =
165 (y + y_offset) * job->width * BYTES_PER_PIXEL +
166 (x + x_offset) * BYTES_PER_PIXEL;
167 const unsigned red = data[offset + 2];
168 const unsigned green = data[offset + 1];
169 const unsigned blue = data[offset];
170
171 const unsigned gray = rgb_to_grayscale(red, green, blue);
172 // The [0, 256) grayscale measurement can be quantized into 16
173 // 16-stride buckets. I.e. [0, 16) as bucket 1, [16, 32) as bucket 2,
174 // … Drawing a threshold at 240, and considering only the last bucket
175 // to be white when converting to monochrome empirically seems to
176 // generate reasonable results.
177 const unsigned pixel = gray >= 240;
178
179 index |= pixel << (y_offset * x_stride + x_offset);
180 }
181 }
182
183 gvputs(job, tiles[index]);
184 }
185 gvputc(job, '\n');
186 }
187}
188
190static void process4up(GVJ_t *job) {
191 // block characters from the “Amstrad CPC character set”
192 const char *tiles[] = {" ", "▘", "▝", "▀", "▖", "▍", "▞", "▛",
193 "▗", "▚", "▐", "▜", "▃", "▙", "▟", "█"};
194 const unsigned y_stride = 2;
195 const unsigned x_stride = 2;
196 assert(sizeof(tiles) / sizeof(tiles[0]) == 1 << (y_stride * x_stride));
197 processNup(job, y_stride, x_stride, tiles);
198}
199
201static void process6up(GVJ_t *job) {
202 // the “Teletext G1 Block Mosaics Set”
203 const char *tiles[] = {" ", "🬀", "🬁", "🬂", "🬃", "🬄", "🬅", "🬆", "🬇", "🬈", "🬉",
204 "🬊", "🬋", "🬌", "🬍", "🬎", "🬏", "🬐", "🬑", "🬒", "🬓", "▌",
205 "🬔", "🬕", "🬖", "🬗", "🬘", "🬙", "🬚", "🬛", "🬜", "🬝", "🬞",
206 "🬟", "🬠", "🬡", "🬢", "🬣", "🬤", "🬥", "🬦", "🬧", "▐", "🬨",
207 "🬩", "🬪", "🬫", "🬬", "🬭", "🬮", "🬯", "🬰", "🬱", "🬲", "🬳",
208 "🬴", "🬵", "🬶", "🬷", "🬸", "🬹", "🬺", "🬻", "█"};
209 const unsigned y_stride = 3;
210 const unsigned x_stride = 2;
211 assert(sizeof(tiles) / sizeof(tiles[0]) == 1 << (y_stride * x_stride));
212 processNup(job, y_stride, x_stride, tiles);
213}
214
216static void process8up(GVJ_t *job) {
217 // the Unicode “Braille Patterns” block
218 const char *tiles[] = {
219 " ", "⠁", "⠈", "⠉", "⠂", "⠃", "⠊", "⠋", "⠐", "⠑", "⠘", "⠙", "⠒", "⠓", "⠚",
220 "⠛", "⠄", "⠅", "⠌", "⠍", "⠆", "⠇", "⠎", "⠏", "⠔", "⠕", "⠜", "⠝", "⠖", "⠗",
221 "⠞", "⠟", "⠠", "⠡", "⠨", "⠩", "⠢", "⠣", "⠪", "⠫", "⠰", "⠱", "⠸", "⠹", "⠲",
222 "⠳", "⠺", "⠻", "⠤", "⠥", "⠬", "⠭", "⠦", "⠧", "⠮", "⠯", "⠴", "⠵", "⠼", "⠽",
223 "⠶", "⠷", "⠾", "⠿", "⡀", "⡁", "⡈", "⡉", "⡂", "⡃", "⡊", "⡋", "⡐", "⡑", "⡘",
224 "⡙", "⡒", "⡓", "⡚", "⡛", "⡄", "⡅", "⡌", "⡍", "⡆", "⡇", "⡎", "⡏", "⡔", "⡕",
225 "⡜", "⡝", "⡖", "⡗", "⡞", "⡟", "⡠", "⡡", "⡨", "⡩", "⡢", "⡣", "⡪", "⡫", "⡰",
226 "⡱", "⡸", "⡹", "⡲", "⡳", "⡺", "⡻", "⡤", "⡥", "⡬", "⡭", "⡦", "⡧", "⡮", "⡯",
227 "⡴", "⡵", "⡼", "⡽", "⡶", "⡷", "⡾", "⡿", "⢀", "⢁", "⢈", "⢉", "⢂", "⢃", "⢊",
228 "⢋", "⢐", "⢑", "⢘", "⢙", "⢒", "⢓", "⢚", "⢛", "⢄", "⢅", "⢌", "⢍", "⢆", "⢇",
229 "⢎", "⢏", "⢔", "⢕", "⢜", "⢝", "⢖", "⢗", "⢞", "⢟", "⢠", "⢡", "⢨", "⢩", "⢢",
230 "⢣", "⢪", "⢫", "⢰", "⢱", "⢸", "⢹", "⢲", "⢳", "⢺", "⢻", "⢤", "⢥", "⢬", "⢭",
231 "⢦", "⢧", "⢮", "⢯", "⢴", "⢵", "⢼", "⢽", "⢶", "⢷", "⢾", "⢿", "⣀", "⣁", "⣈",
232 "⣉", "⣂", "⣃", "⣊", "⣋", "⣐", "⣑", "⣘", "⣙", "⣒", "⣓", "⣚", "⣛", "⣄", "⣅",
233 "⣌", "⣍", "⣆", "⣇", "⣎", "⣏", "⣔", "⣕", "⣜", "⣝", "⣖", "⣗", "⣞", "⣟", "⣠",
234 "⣡", "⣨", "⣩", "⣢", "⣣", "⣪", "⣫", "⣰", "⣱", "⣸", "⣹", "⣲", "⣳", "⣺", "⣻",
235 "⣤", "⣥", "⣬", "⣭", "⣦", "⣧", "⣮", "⣯", "⣴", "⣵", "⣼", "⣽", "⣶", "⣷", "⣾",
236 "⣿"};
237 const unsigned y_stride = 4;
238 const unsigned x_stride = 2;
239 assert(sizeof(tiles) / sizeof(tiles[0]) == 1 << (y_stride * x_stride));
240 processNup(job, y_stride, x_stride, tiles);
241}
242
244 .format = process3,
245};
246
250
254
258
262
264 .default_dpi = {96, 96},
265};
266
268 {8, "vt:cairo", 0, &engine3, &device_features},
269 {1 << 24, "vt-24bit:cairo", 0, &engine24, &device_features},
270 {4, "vt-4up:cairo", 0, &engine4up, &device_features},
271 {6, "vt-6up:cairo", 0, &engine6up, &device_features},
272 {7, "vt-8up:cairo", 0, &engine8up, &device_features},
273 {0},
274};
275
277 {API_device, device_types},
278 {(api_t)0, 0},
279};
280
281#ifdef GVDLL
282#define GVPLUGIN_VT_API __declspec(dllexport)
283#else
284#define GVPLUGIN_VT_API
285#endif
286
node NULL
Definition grammar.y:163
Arithmetic helper functions.
@ BYTES_PER_PIXEL
Definition gv_math.h:87
swig_ptr_object_handlers offset
Definition gv_php.cpp:5907
api_t
Definition gvcext.h:32
int gvputc(GVJ_t *job, int c)
Definition gvdevice.c:291
int gvputs(GVJ_t *job, const char *s)
Definition gvdevice.c:264
void gvprintf(GVJ_t *job, const char *format,...)
Definition gvdevice.c:395
static void process(GVJ_t *job, int color_depth)
Definition gvplugin_vt.c:58
static void process6up(GVJ_t *job)
draw a 6-pixels-per-character monochrome image
static gvplugin_installed_t device_types[]
static gvdevice_engine_t engine8up
#define GVPLUGIN_VT_API
static gvdevice_features_t device_features
static void process8up(GVJ_t *job)
draw a 8-pixels-per-character monochrome image
static unsigned rgb_to_grayscale(unsigned red, unsigned green, unsigned blue)
convert an RGB color to grayscale
static gvdevice_engine_t engine24
static const color_t COLORS[]
ANSI 3-bit colors.
Definition gvplugin_vt.c:23
static void process4up(GVJ_t *job)
draw a 4-pixels-per-character monochrome image
static gvdevice_engine_t engine3
static void process24(GVJ_t *job)
static void process3(GVJ_t *job)
static gvdevice_engine_t engine4up
static void processNup(GVJ_t *job, unsigned y_stride, unsigned x_stride, const char **tiles)
static unsigned distance(const color_t base, unsigned red, unsigned green, unsigned blue)
a metric of “closeness” to a given color
Definition gvplugin_vt.c:35
GVPLUGIN_VT_API gvplugin_library_t gvplugin_vt_LTX_library
static gvplugin_api_t apis[]
static gvdevice_engine_t engine6up
static unsigned get_color(unsigned red, unsigned green, unsigned blue)
find closest ANSI color
Definition gvplugin_vt.c:45
unsigned char * imagedata
location of imagedata
Definition gvcjob.h:297
unsigned int width
Definition gvcjob.h:327
unsigned int height
Definition gvcjob.h:328
an ANSI color
Definition gvplugin_vt.c:14
unsigned green
Definition gvplugin_vt.c:17
unsigned blue
Definition gvplugin_vt.c:18
unsigned value
Definition gvplugin_vt.c:15
unsigned red
Definition gvplugin_vt.c:16
Definition legal.c:50
void(* format)(GVJ_t *firstjob)
ingroup plugin_api
Definition gvplugin.h:35