Graphviz 15.1.1~dev.20260628.0906
Loading...
Searching...
No Matches
gvplugin_vt.c
Go to the documentation of this file.
1
3
4#include "config.h"
5
6#include <assert.h>
7#include <gvc/gvplugin.h>
9#include <limits.h>
10#include <stddef.h>
11#include <util/gv_math.h>
12
13#include <gvc/gvio.h>
14
16typedef struct {
17 unsigned value;
18 unsigned red;
19 unsigned green;
20 unsigned blue;
21
22} color_t;
23
25static const color_t COLORS[] = {
26 {0, 0x00, 0x00, 0x00},
27 {1, 0xff, 0x00, 0x00},
28 {2, 0x00, 0xff, 0x00},
29 {3, 0xff, 0xff, 0x00},
30 {4, 0x00, 0x00, 0xff},
31 {5, 0xff, 0x00, 0xff},
32 {6, 0x00, 0xff, 0xff},
33 {7, 0xff, 0xff, 0xff},
34};
35
37static unsigned distance(const color_t base, unsigned red, unsigned green,
38 unsigned blue) {
39 unsigned diff = 0;
40 diff += red > base.red ? red - base.red : base.red - red;
41 diff += green > base.green ? green - base.green : base.green - green;
42 diff += blue > base.blue ? blue - base.blue : base.blue - blue;
43 return diff;
44}
45
47static unsigned get_color(unsigned red, unsigned green, unsigned blue) {
48 unsigned winner = 0;
49 unsigned diff = UINT_MAX;
50 for (size_t i = 0; i < sizeof(COLORS) / sizeof(COLORS[0]); ++i) {
51 unsigned d = distance(COLORS[i], red, green, blue);
52 if (d < diff) {
53 diff = d;
54 winner = COLORS[i].value;
55 }
56 }
57 return winner;
58}
59
60static void process(GVJ_t *job, int color_depth) {
61
62 unsigned char *data = job->imagedata;
63
64 assert(color_depth == 3 || color_depth == 24);
65
66 for (unsigned y = 0; y < job->height; y += 2) {
67 for (unsigned x = 0; x < job->width; ++x) {
68
69 {
70 // extract the upper pixel
71 unsigned offset =
72 y * job->width * BYTES_PER_PIXEL + x * BYTES_PER_PIXEL;
73 unsigned red = data[offset + 2];
74 unsigned green = data[offset + 1];
75 unsigned blue = data[offset];
76
77 // use this to select a foreground color
78 if (color_depth == 3) {
79 unsigned fg = get_color(red, green, blue);
80 gvprintf(job, "\033[3%um", fg);
81 } else {
82 assert(color_depth == 24);
83 gvprintf(job, "\033[38;2;%u;%u;%um", red, green, blue);
84 }
85 }
86
87 {
88 // extract the lower pixel
89 unsigned red = 0;
90 unsigned green = 0;
91 unsigned blue = 0;
92 if (y + 1 < job->height) {
93 unsigned offset =
94 (y + 1) * job->width * BYTES_PER_PIXEL + x * BYTES_PER_PIXEL;
95 red = data[offset + 2];
96 green = data[offset + 1];
97 blue = data[offset];
98 }
99
100 // use this to select a background color
101 if (color_depth == 3) {
102 unsigned bg = get_color(red, green, blue);
103 gvprintf(job, "\033[4%um", bg);
104 } else {
105 assert(color_depth == 24);
106 gvprintf(job, "\033[48;2;%u;%u;%um", red, green, blue);
107 }
108 }
109
110 // print unicode “upper half block” to effectively do two rows of
111 // pixels per one terminal row
112 gvprintf(job, "▀\033[0m");
113 }
114 gvprintf(job, "\n");
115 }
116}
117
118static void process3(GVJ_t *job) { process(job, 3); }
119
120static void process24(GVJ_t *job) { process(job, 24); }
121
123static unsigned rgb_to_grayscale(unsigned red, unsigned green, unsigned blue) {
124
127
128 const double r_linear = red / 255.0;
129 const double g_linear = green / 255.0;
130 const double b_linear = blue / 255.0;
131
132 const double y_linear =
133 0.2126 * r_linear + 0.7152 * g_linear + 0.0722 * b_linear;
134 return (unsigned)(y_linear * 255.999);
135}
136
143static void processNup(GVJ_t *job, unsigned y_stride, unsigned x_stride,
144 const char **tiles) {
145 assert(y_stride > 0);
146 assert(x_stride > 0);
147 assert(tiles != NULL);
148 for (unsigned i = 0; i < y_stride; ++i) {
149 for (unsigned j = 0; j < x_stride; ++j) {
150 assert(tiles[i * x_stride + j] != NULL && "missing or not enough tiles");
151 }
152 }
153
154 unsigned char *data = job->imagedata;
155
156 for (unsigned y = 0; y < job->height; y += y_stride) {
157 for (unsigned x = 0; x < job->width; x += x_stride) {
158
159 unsigned index = 0;
160
161 for (unsigned y_offset = 0;
162 y + y_offset < job->height && y_offset < y_stride; ++y_offset) {
163 for (unsigned x_offset = 0;
164 x + x_offset < job->width && x_offset < x_stride; ++x_offset) {
165
166 const unsigned offset =
167 (y + y_offset) * job->width * BYTES_PER_PIXEL +
168 (x + x_offset) * BYTES_PER_PIXEL;
169 const unsigned red = data[offset + 2];
170 const unsigned green = data[offset + 1];
171 const unsigned blue = data[offset];
172
173 const unsigned gray = rgb_to_grayscale(red, green, blue);
174 // The [0, 256) grayscale measurement can be quantized into 16
175 // 16-stride buckets. I.e. [0, 16) as bucket 1, [16, 32) as bucket 2,
176 // … Drawing a threshold at 240, and considering only the last bucket
177 // to be white when converting to monochrome empirically seems to
178 // generate reasonable results.
179 const unsigned pixel = gray >= 240;
180
181 index |= pixel << (y_offset * x_stride + x_offset);
182 }
183 }
184
185 gvputs(job, tiles[index]);
186 }
187 gvputc(job, '\n');
188 }
189}
190
192static void process4up(GVJ_t *job) {
193 // block characters from the “Amstrad CPC character set”
194 const char *tiles[] = {" ", "▘", "▝", "▀", "▖", "▍", "▞", "▛",
195 "▗", "▚", "▐", "▜", "▃", "▙", "▟", "█"};
196 const unsigned y_stride = 2;
197 const unsigned x_stride = 2;
198 assert(sizeof(tiles) / sizeof(tiles[0]) == 1 << (y_stride * x_stride));
199 processNup(job, y_stride, x_stride, tiles);
200}
201
203static void process6up(GVJ_t *job) {
204 // the “Teletext G1 Block Mosaics Set”
205 const char *tiles[] = {" ", "🬀", "🬁", "🬂", "🬃", "🬄", "🬅", "🬆", "🬇", "🬈", "🬉",
206 "🬊", "🬋", "🬌", "🬍", "🬎", "🬏", "🬐", "🬑", "🬒", "🬓", "▌",
207 "🬔", "🬕", "🬖", "🬗", "🬘", "🬙", "🬚", "🬛", "🬜", "🬝", "🬞",
208 "🬟", "🬠", "🬡", "🬢", "🬣", "🬤", "🬥", "🬦", "🬧", "▐", "🬨",
209 "🬩", "🬪", "🬫", "🬬", "🬭", "🬮", "🬯", "🬰", "🬱", "🬲", "🬳",
210 "🬴", "🬵", "🬶", "🬷", "🬸", "🬹", "🬺", "🬻", "█"};
211 const unsigned y_stride = 3;
212 const unsigned x_stride = 2;
213 assert(sizeof(tiles) / sizeof(tiles[0]) == 1 << (y_stride * x_stride));
214 processNup(job, y_stride, x_stride, tiles);
215}
216
218static void process8up(GVJ_t *job, const char **tiles) {
219 const unsigned y_stride = 4;
220 const unsigned x_stride = 2;
221 assert(256 == 1 << (y_stride * x_stride));
222 processNup(job, y_stride, x_stride, tiles);
223}
224
226static void process8up1(GVJ_t *job) {
227 // the Unicode “Braille Patterns” block
228 const char *tiles[] = {
229 " ", "⠁", "⠈", "⠉", "⠂", "⠃", "⠊", "⠋", "⠐", "⠑", "⠘", "⠙", "⠒", "⠓", "⠚",
230 "⠛", "⠄", "⠅", "⠌", "⠍", "⠆", "⠇", "⠎", "⠏", "⠔", "⠕", "⠜", "⠝", "⠖", "⠗",
231 "⠞", "⠟", "⠠", "⠡", "⠨", "⠩", "⠢", "⠣", "⠪", "⠫", "⠰", "⠱", "⠸", "⠹", "⠲",
232 "⠳", "⠺", "⠻", "⠤", "⠥", "⠬", "⠭", "⠦", "⠧", "⠮", "⠯", "⠴", "⠵", "⠼", "⠽",
233 "⠶", "⠷", "⠾", "⠿", "⡀", "⡁", "⡈", "⡉", "⡂", "⡃", "⡊", "⡋", "⡐", "⡑", "⡘",
234 "⡙", "⡒", "⡓", "⡚", "⡛", "⡄", "⡅", "⡌", "⡍", "⡆", "⡇", "⡎", "⡏", "⡔", "⡕",
235 "⡜", "⡝", "⡖", "⡗", "⡞", "⡟", "⡠", "⡡", "⡨", "⡩", "⡢", "⡣", "⡪", "⡫", "⡰",
236 "⡱", "⡸", "⡹", "⡲", "⡳", "⡺", "⡻", "⡤", "⡥", "⡬", "⡭", "⡦", "⡧", "⡮", "⡯",
237 "⡴", "⡵", "⡼", "⡽", "⡶", "⡷", "⡾", "⡿", "⢀", "⢁", "⢈", "⢉", "⢂", "⢃", "⢊",
238 "⢋", "⢐", "⢑", "⢘", "⢙", "⢒", "⢓", "⢚", "⢛", "⢄", "⢅", "⢌", "⢍", "⢆", "⢇",
239 "⢎", "⢏", "⢔", "⢕", "⢜", "⢝", "⢖", "⢗", "⢞", "⢟", "⢠", "⢡", "⢨", "⢩", "⢢",
240 "⢣", "⢪", "⢫", "⢰", "⢱", "⢸", "⢹", "⢲", "⢳", "⢺", "⢻", "⢤", "⢥", "⢬", "⢭",
241 "⢦", "⢧", "⢮", "⢯", "⢴", "⢵", "⢼", "⢽", "⢶", "⢷", "⢾", "⢿", "⣀", "⣁", "⣈",
242 "⣉", "⣂", "⣃", "⣊", "⣋", "⣐", "⣑", "⣘", "⣙", "⣒", "⣓", "⣚", "⣛", "⣄", "⣅",
243 "⣌", "⣍", "⣆", "⣇", "⣎", "⣏", "⣔", "⣕", "⣜", "⣝", "⣖", "⣗", "⣞", "⣟", "⣠",
244 "⣡", "⣨", "⣩", "⣢", "⣣", "⣪", "⣫", "⣰", "⣱", "⣸", "⣹", "⣲", "⣳", "⣺", "⣻",
245 "⣤", "⣥", "⣬", "⣭", "⣦", "⣧", "⣮", "⣯", "⣴", "⣵", "⣼", "⣽", "⣶", "⣷", "⣾",
246 "⣿"};
247 process8up(job, tiles);
248}
249
251static void process8up2(GVJ_t *job) {
252 // the Unicode octants block
253 const char *tiles[] = {
254 " ", "𜺨", "𜺫", "🮂", "𜴀", "▘", "𜴁", "𜴂", "𜴃",
255 "𜴄", "▝", "𜴅", "𜴆", "𜴇", "𜴈", "▀", "𜴉", "𜴊",
256 "𜴋", "𜴌", "🯦", "𜴍", "𜴎", "𜴏", "𜴐", "𜴑", "𜴒",
257 "𜴓", "𜴔", "𜴕", "𜴖", "𜴗", "𜴘", "𜴙", "𜴚", "𜴛",
258 "𜴜", "𜴝", "𜴞", "𜴟", "🯧", "𜴠", "𜴡", "𜴢", "𜴣",
259 "𜴤", "𜴥", "𜴦", "𜴧", "𜴨", "𜴩", "𜴪", "𜴫", "𜴬",
260 "𜴭", "𜴮", "𜴯", "𜴰", "𜴱", "𜴲", "𜴳", "𜴴", "𜴵",
261 "🮅", "𜺣", "𜴶", "𜴷", "𜴸", "𜴹", "𜴺", "𜴻", "𜴼",
262 "𜴽", "𜴾", "𜴿", "𜵀", "𜵁", "𜵂", "𜵃", "𜵄", "▖",
263 "𜵅", "𜵆", "𜵇", "𜵈", "▌", "𜵉", "𜵊", "𜵋", "𜵌",
264 "▞", "𜵍", "𜵎", "𜵏", "𜵐", "▛", "𜵑", "𜵒", "𜵓",
265 "𜵔", "𜵕", "𜵖", "𜵗", "𜵘", "𜵙", "𜵚", "𜵛", "𜵜",
266 "𜵝", "𜵞", "𜵟", "𜵠", "𜵡", "𜵢", "𜵣", "𜵤", "𜵥",
267 "𜵦", "𜵧", "𜵨", "𜵩", "𜵪", "𜵫", "𜵬", "𜵭", "𜵮",
268 "𜵯", "𜵰", "𜺠", "𜵱", "𜵲", "𜵳", "𜵴", "𜵵", "𜵶",
269 "𜵷", "𜵸", "𜵹", "𜵺", "𜵻", "𜵼", "𜵽", "𜵾", "𜵿",
270 "𜶀", "𜶁", "𜶂", "𜶃", "𜶄", "𜶅", "𜶆", "𜶇", "𜶈",
271 "𜶉", "𜶊", "𜶋", "𜶌", "𜶍", "𜶎", "𜶏", "▗", "𜶐",
272 "𜶑", "𜶒", "𜶓", "▚", "𜶔", "𜶕", "𜶖", "𜶗", "▐",
273 "𜶘", "𜶙", "𜶚", "𜶛", "▜", "𜶜", "𜶝", "𜶞", "𜶟",
274 "𜶠", "𜶡", "𜶢", "𜶣", "𜶤", "𜶥", "𜶦", "𜶧", "𜶨",
275 "𜶩", "𜶪", "𜶫", "▂", "𜶬", "𜶭", "𜶮", "𜶯", "𜶰",
276 "𜶱", "𜶲", "𜶳", "𜶴", "𜶵", "𜶶", "𜶷", "𜶸", "𜶹",
277 "𜶺", "𜶻", "𜶼", "𜶽", "𜶾", "𜶿", "𜷀", "𜷁", "𜷂",
278 "𜷃", "𜷄", "𜷅", "𜷆", "𜷇", "𜷈", "𜷉", "𜷊", "𜷋",
279 "𜷌", "𜷍", "𜷎", "𜷏", "𜷐", "𜷑", "𜷒", "𜷓", "𜷔",
280 "𜷕", "𜷖", "𜷗", "𜷘", "𜷙", "𜷚", "▄", "𜷛", "𜷜",
281 "𜷝", "𜷞", "▙", "𜷟", "𜷠", "𜷡", "𜷢", "▟", "𜷣",
282 "▆", "𜷤", "𜷥", "█",
283 };
284 process8up(job, tiles);
285}
286
288 .format = process3,
289};
290
294
298
302
306
310
312 .default_dpi = {96, 96},
313};
314
315enum {
316 PPC2_3, // 2 pixels per cell, 3-bit color
317 PPC2_24, // 2 pixels per cell, 24-bit color
318 PPC4, // 4 pixels per cell
319 PPC6, // 6 pixels per cell
320 PPC8_BRAILLE, // 8 pixels per cell with Braille
321 PPC8_OCTANTS, // 8 pixels per cell with octants
322};
323
325 {PPC2_3, "vt:cairo", 0, &engine3, &device_features},
326 {PPC2_24, "vt-24bit:cairo", 0, &engine24, &device_features},
327 {PPC4, "vt-4up:cairo", 0, &engine4up, &device_features},
328 {PPC6, "vt-6up:cairo", 0, &engine6up, &device_features},
329 {PPC8_BRAILLE, "vt-8up:cairo", 0, &engine8up1, &device_features},
330 {PPC8_OCTANTS, "vt-8up2:cairo", 0, &engine8up2, &device_features},
331 {0},
332};
333
335 {API_device, device_types},
336 {(api_t)0, 0},
337};
338
339#ifdef GVDLL
340#define GVPLUGIN_VT_API __declspec(dllexport)
341#else
342#define GVPLUGIN_VT_API
343#endif
344
node NULL
Definition grammar.y:181
Arithmetic helper functions.
@ BYTES_PER_PIXEL
Definition gv_math.h:94
api_t
Definition gvcext.h:32
int gvputc(GVJ_t *job, int c)
Definition gvdevice.c:298
int gvputs(GVJ_t *job, const char *s)
Definition gvdevice.c:266
void gvprintf(GVJ_t *job, const char *format,...)
Definition gvdevice.c:402
static gvdevice_engine_t engine8up2
static void process(GVJ_t *job, int color_depth)
Definition gvplugin_vt.c:60
static void process6up(GVJ_t *job)
draw a 6-pixels-per-character monochrome image
static gvplugin_installed_t device_types[]
#define GVPLUGIN_VT_API
static void process8up2(GVJ_t *job)
draw a 8-pixels-per-character monochrome image with octant characters
static void process8up(GVJ_t *job, const char **tiles)
draw a 8-pixels-per-character monochrome image
static gvdevice_features_t device_features
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:25
static void process4up(GVJ_t *job)
draw a 4-pixels-per-character monochrome image
static gvdevice_engine_t engine8up1
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 void process8up1(GVJ_t *job)
draw a 8-pixels-per-character monochrome image with Braille characters
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:37
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:47
@ PPC8_BRAILLE
@ PPC8_OCTANTS
@ PPC4
@ PPC6
@ PPC2_3
@ PPC2_24
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:16
unsigned green
Definition gvplugin_vt.c:19
unsigned blue
Definition gvplugin_vt.c:20
unsigned value
Definition gvplugin_vt.c:17
unsigned red
Definition gvplugin_vt.c:18
void(* format)(GVJ_t *firstjob)
ingroup plugin_api
Definition gvplugin.h:35