Skip to content

Commit cc1d0ec

Browse files
committed
Add braille character set.
1 parent b2c6d63 commit cc1d0ec

File tree

4 files changed

+136
-57
lines changed

4 files changed

+136
-57
lines changed

.clang-format

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ BreakBeforeTernaryOperators: false
3535
BreakConstructorInitializersBeforeComma: false
3636
BreakAfterJavaFieldAnnotations: false
3737
BreakStringLiterals: false
38-
ColumnLimit: 0
38+
ColumnLimit: 100
3939
CommentPragmas: '^ IWYU pragma:'
4040
ConstructorInitializerAllOnOneLineOrOnePerLine: false
4141
ConstructorInitializerIndentWidth: 8

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,11 @@ make windows-cross
3434

3535
The following command-line arguments can be supplied:
3636
- `-nocolor`: Disable color.
37-
- `-nograd`: Disable text gradients, exclusively use # or █.
37+
- `-nograd`: Disable text gradients, exclusively use fully filled-in pixels.
3838
- `-nobold`: Disable bold text.
39-
- `-unicode`: Use [unicode block elements](https://en.wikipedia.org/wiki/Block_Elements) instead of ASCII characters.
39+
- `-chars <ascii|block|braille>`: Use ASCII characters, [unicode block elements](https://en.wikipedia.org/wiki/Block_Elements), or [braille patterns](https://en.wikipedia.org/wiki/Braille_Patterns).
4040
- `-erase`: Erase previous frame instead of overwriting. May cause a strobe effect.
41+
- `-fixgamma`: Scale gamma to offset darkening of pixels caused by using a text gradient. Use with caution, as colors become distorted.
4142
- `-scaling <n>`: Set resolution. Smaller numbers denote a larger display. A scale of 4 is used by default, and should work flawlessly on all terminals. Most terminals (excluding Windows CMD) should manage with scales up to and including 2.
4243

4344
## Controls

src/config.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@
7373
#define PACKAGE_NAME "Doom ASCII"
7474

7575
/* Define to the full name and version of this package. */
76-
#define PACKAGE_STRING "Doom ASCII 0.2.0"
76+
#define PACKAGE_STRING "Doom ASCII 0.3.0"
7777

7878
/* Define to the one symbol short name of this package. */
7979
#define PACKAGE_TARNAME "doom_ascii.tar"
@@ -91,7 +91,7 @@
9191
#define STDC_HEADERS 1
9292

9393
/* Version number of package */
94-
#define VERSION 0.2.0
94+
#define VERSION 0.3.0
9595

9696
/* Define to 1 if you want to compile the unmodified code */
9797
#undef ORIGCODE

src/doomgeneric_ascii.c

Lines changed: 130 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -41,24 +41,20 @@
4141
#ifdef OS_WINDOWS
4242
#define CLK 0
4343

44-
#define WINDOWS_CALL(cond, format) \
45-
do { \
46-
if (UNLIKELY(cond)) \
47-
winError(format); \
44+
#define WINDOWS_CALL(cond, format) \
45+
do { \
46+
if (UNLIKELY(cond)) \
47+
winError(format); \
4848
} while (0)
4949

5050
static void winError(char *const format)
5151
{
5252
LPVOID lpMsgBuf;
5353
const DWORD dw = GetLastError();
5454
errno = dw;
55-
FormatMessage(
56-
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
57-
NULL,
58-
dw,
59-
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
60-
(LPTSTR)&lpMsgBuf,
61-
0, NULL);
55+
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM
56+
| FORMAT_MESSAGE_IGNORE_INSERTS,
57+
NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, NULL);
6258
I_Error(format, lpMsgBuf);
6359
}
6460

@@ -89,37 +85,36 @@ static int clock_gettime(const int p, struct timespec *const spec)
8985
#define UNLIKELY(x) (x)
9086
#endif
9187

92-
#define CALL(stmt, format) \
93-
do { \
94-
if (UNLIKELY(stmt)) \
95-
I_Error(format, errno); \
88+
#define CALL(stmt, format) \
89+
do { \
90+
if (UNLIKELY(stmt)) \
91+
I_Error(format, errno); \
9692
} while (0)
9793
#define CALL_STDOUT(stmt, format) CALL((stmt) == EOF, format)
9894

99-
#define BUF_ITOA(buf, byte) \
100-
do { \
101-
*(buf)++ = '0' + (byte) / 100u; \
102-
*(buf)++ = '0' + (byte) / 10u % 10u; \
103-
*(buf)++ = '0' + (byte) % 10u; \
95+
#define BUF_ITOA(buf, byte) \
96+
do { \
97+
*(buf)++ = '0' + (byte) / 100u; \
98+
*(buf)++ = '0' + (byte) / 10u % 10u; \
99+
*(buf)++ = '0' + (byte) % 10u; \
104100
} while (0)
105-
106-
#define BUF_MEMCPY(buf, s, len) \
107-
do { \
108-
memcpy(buf, s, len); \
109-
(buf) += (len); \
101+
#define BUF_MEMCPY(buf, s, len) \
102+
do { \
103+
memcpy(buf, s, len); \
104+
(buf) += (len); \
110105
} while (0)
111-
112-
#define BUF_PUTS(buf, s) \
113-
do { \
114-
BUF_MEMCPY(buf, s, sizeof(s) - 1); \
106+
#define BUF_PUTS(buf, s) \
107+
do { \
108+
BUF_MEMCPY(buf, s, static_strlen(s)); \
115109
} while (0)
116-
117-
#define BUF_PUTCHAR(buf, c) \
118-
do { \
119-
*(buf)++ = c; \
110+
#define BUF_PUTCHAR(buf, c) \
111+
do { \
112+
*(buf)++ = c; \
120113
} while (0)
121114

122-
#define GRAD_LEN (sizeof(grad) - 1)
115+
#define static_strlen(s) (sizeof(s) - 1)
116+
117+
#define GRAD_LEN static_strlen(grad)
123118
#define EVENT_BUFFER_LEN (INPUT_BUFFER_LEN * 2u - 1u)
124119

125120
enum {
@@ -128,8 +123,38 @@ enum {
128123
RGB_SUM_MAX = 776U,
129124
};
130125

131-
static const char grad[] = " .'`^\",:;Il!i><~+_-?][}{1)(|\\/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$";
126+
static const char grad[] =
127+
" .'`^\",:;Il!i><~+_-?][}{1)(|\\/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$";
132128
static const char unicode_grad[] = "\u2591\u2592\u2593\u2588";
129+
const char *braille_grads[] = { "\u2801\u2802\u2804\u2808\u2810\u2820\u2840\u2880",
130+
"\u2803\u2805\u2806\u2809\u280a\u280c\u2811\u2812\u2814\u2818\u2821\u2822\u2824\u2828\u2830\u2841\u2842\u2844\u2848\u2850\u2860\u2881\u2882\u2884\u2888\u2890\u28a0\u28c0",
131+
"\u2807\u280b\u280d\u280e\u2813\u2815\u2816\u2819\u281a\u281c\u2823\u2825\u2826\u2829\u282a\u282c\u2831\u2832\u2834\u2838\u2843\u2845\u2846\u2849\u284a\u284c\u2851\u2852\u2854\u2858\u2861\u2862\u2864\u2868\u2870\u2883\u2885\u2886\u2889\u288a\u288c\u2891\u2892\u2894\u2898\u28a1\u28a2\u28a4\u28a8\u28b0\u28c1\u28c2\u28c4\u28c8\u28d0\u28e0",
132+
"\u280f\u2817\u281b\u281d\u281e\u2827\u282b\u282d\u282e\u2833\u2835\u2836\u2839\u283a\u283c\u2847\u284b\u284d\u284e\u2853\u2855\u2856\u2859\u285a\u285c\u2863\u2865\u2866\u2869\u286a\u286c\u2871\u2872\u2874\u2878\u2887\u288b\u288d\u288e\u2893\u2895\u2896\u2899\u289a\u289c\u28a3\u28a5\u28a6\u28a9\u28aa\u28ac\u28b1\u28b2\u28b4\u28b8\u28c3\u28c5\u28c6\u28c9\u28ca\u28cc\u28d1\u28d2\u28d4\u28d8\u28e1\u28e2\u28e4\u28e8\u28f0",
133+
"\u281f\u282f\u2837\u283b\u283d\u283e\u284f\u2857\u285b\u285d\u285e\u2867\u286b\u286d\u286e\u2873\u2875\u2876\u2879\u287a\u287c\u288f\u2897\u289b\u289d\u289e\u28a7\u28ab\u28ad\u28ae\u28b3\u28b5\u28b6\u28b9\u28ba\u28bc\u28c7\u28cb\u28cd\u28ce\u28d3\u28d5\u28d6\u28d9\u28da\u28dc\u28e3\u28e5\u28e6\u28e9\u28ea\u28ec\u28f1\u28f2\u28f4\u28f8",
134+
"\u283f\u285f\u286f\u2877\u287b\u287d\u287e\u289f\u28af\u28b7\u28bb\u28bd\u28be\u28cf\u28d7\u28db\u28dd\u28de\u28e7\u28eb\u28ed\u28ee\u28f3\u28f5\u28f6\u28f9\u28fa\u28fc",
135+
"\u287f\u28bf\u28df\u28ef\u28f7\u28fb\u28fd\u28fe", "\u28ff" };
136+
const size_t braille_grad_lengths[] = { static_strlen(braille_grads[0]),
137+
static_strlen(braille_grads[1]), static_strlen(braille_grads[2]),
138+
static_strlen(braille_grads[3]), static_strlen(braille_grads[4]),
139+
static_strlen(braille_grads[5]), static_strlen(braille_grads[6]),
140+
static_strlen(braille_grads[7]) };
141+
142+
/* print(','.join(str(int(((i / 255)**0.5) * 255)) for i in range(256))) */
143+
static const uint8_t byte_sqrt[] = { 0, 15, 22, 27, 31, 35, 39, 42, 45, 47, 50, 52, 55, 57, 59, 61,
144+
63, 65, 67, 69, 71, 73, 74, 76, 78, 79, 81, 82, 84, 85, 87, 88, 90, 91, 93, 94, 95, 97, 98,
145+
99, 100, 102, 103, 104, 105, 107, 108, 109, 110, 111, 112, 114, 115, 116, 117, 118, 119,
146+
120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137,
147+
138, 139, 140, 141, 141, 142, 143, 144, 145, 146, 147, 148, 148, 149, 150, 151, 152, 153,
148+
153, 154, 155, 156, 157, 158, 158, 159, 160, 161, 162, 162, 163, 164, 165, 165, 166, 167,
149+
168, 168, 169, 170, 171, 171, 172, 173, 174, 174, 175, 176, 177, 177, 178, 179, 179, 180,
150+
181, 182, 182, 183, 184, 184, 185, 186, 186, 187, 188, 188, 189, 190, 190, 191, 192, 192,
151+
193, 194, 194, 195, 196, 196, 197, 198, 198, 199, 200, 200, 201, 201, 202, 203, 203, 204,
152+
205, 205, 206, 206, 207, 208, 208, 209, 210, 210, 211, 211, 212, 213, 213, 214, 214, 215,
153+
216, 216, 217, 217, 218, 218, 219, 220, 220, 221, 221, 222, 222, 223, 224, 224, 225, 225,
154+
226, 226, 227, 228, 228, 229, 229, 230, 230, 231, 231, 232, 233, 233, 234, 234, 235, 235,
155+
236, 236, 237, 237, 238, 238, 239, 240, 240, 241, 241, 242, 242, 243, 243, 244, 244, 245,
156+
245, 246, 246, 247, 247, 248, 248, 249, 249, 250, 250, 251, 251, 252, 252, 253, 253, 254,
157+
255 };
133158

134159
struct color_t {
135160
uint32_t b : 8;
@@ -138,6 +163,8 @@ struct color_t {
138163
uint32_t a : 8;
139164
};
140165

166+
enum character_set_t { ASCII, BLOCK, BRAILLE };
167+
141168
static char *output_buffer;
142169
static size_t output_buffer_size;
143170
static struct timespec ts_init;
@@ -147,10 +174,11 @@ static uint16_t event_buffer[EVENT_BUFFER_LEN];
147174
static uint16_t *event_buf_loc;
148175

149176
static bool color_enabled;
150-
static bool unicode_enabled;
177+
static enum character_set_t character_set = ASCII;
151178
static bool gradient_enabled;
152179
static bool bold_enabled;
153180
static bool erase_enabled;
181+
static bool gamma_correct_enabled;
154182

155183
void DG_AtExit(void)
156184
{
@@ -188,7 +216,8 @@ void DG_Init(void)
188216
const HANDLE hInputHandle = GetStdHandle(STD_INPUT_HANDLE);
189217
WINDOWS_CALL(hInputHandle == INVALID_HANDLE_VALUE, "DG_Init: %s");
190218
WINDOWS_CALL(!GetConsoleMode(hInputHandle, &mode), "DG_Init: %s");
191-
mode &= ~(ENABLE_MOUSE_INPUT | ENABLE_WINDOW_INPUT | ENABLE_QUICK_EDIT_MODE | ENABLE_ECHO_INPUT);
219+
mode &= ~(ENABLE_MOUSE_INPUT | ENABLE_WINDOW_INPUT | ENABLE_QUICK_EDIT_MODE
220+
| ENABLE_ECHO_INPUT);
192221
WINDOWS_CALL(!SetConsoleMode(hInputHandle, mode), "DG_Init: %s");
193222
#else
194223
struct termios t;
@@ -202,21 +231,37 @@ void DG_Init(void)
202231
gradient_enabled = M_CheckParm("-nograd") == 0;
203232
bold_enabled = M_CheckParm("-nobold") == 0;
204233
erase_enabled = M_CheckParm("-erase") > 0;
205-
unicode_enabled = M_CheckParm("-unicode") > 0;
234+
gamma_correct_enabled = M_CheckParm("-fixgamma") > 0;
235+
236+
int i = M_CheckParmWithArgs("-chars", 1);
237+
if (i > 0) {
238+
if (!strcmp("ascii", myargv[i + 1])) {
239+
character_set = ASCII;
240+
} else if (!strcmp("block", myargv[i + 1])) {
241+
character_set = BLOCK;
242+
} else if (!strcmp("braille", myargv[i + 1])) {
243+
character_set = BRAILLE;
244+
} else {
245+
I_Error("Unrecognized argument for -chars: '%s'", myargv[i + 1]);
246+
}
247+
}
206248

207-
if (unicode_enabled && !setlocale(LC_ALL, "en_US.UTF-8"))
249+
if (character_set != ASCII && !setlocale(LC_ALL, "en_US.UTF-8"))
208250
I_Error("DG_Init: setlocale error");
209251

210252
/* Longest per-pixel SGR code: \033[38;2;RRR;GGG;BBBm (length 19)
211-
* 3 Chars per pixel (unicode)
253+
* 2 Chars per pixel
212254
* 1 Newline character per line
213255
* 1 NUL terminator
214256
* SGR move cursor code: \033[;H (length 4)
215257
* SGR clear code: \033[0m (length 4)
216258
* SGR bold code: \033[1m (length 4)
217259
* SGR erase code: \033[2J (length 4)
218260
*/
219-
output_buffer_size = ((color_enabled ? 19U : 0U) + (unicode_enabled ? 6U : 2U)) * DOOMGENERIC_RESX * DOOMGENERIC_RESY + DOOMGENERIC_RESY + 1U + 4U + (bold_enabled ? 4U : 0U) + (erase_enabled ? 4U : 0U) + ((color_enabled || bold_enabled) ? 4U : 0U);
261+
output_buffer_size = ((color_enabled ? 19U : 0U) + (character_set == ASCII ? 2U : 6U))
262+
* DOOMGENERIC_RESX * DOOMGENERIC_RESY
263+
+ DOOMGENERIC_RESY + 1U + 4U + (bold_enabled ? 4U : 0U) + (erase_enabled ? 4U : 0U)
264+
+ ((color_enabled || bold_enabled) ? 4U : 0U);
220265
output_buffer = malloc(output_buffer_size);
221266

222267
CALL(clock_gettime(CLK, &ts_init), "DG_Init: clock_gettime error %d");
@@ -246,6 +291,12 @@ void DG_DrawFrame(void)
246291
BUF_PUTS(buf, "\033[1m");
247292
for (row = 0; row < DOOMGENERIC_RESY; row++) {
248293
for (col = 0; col < DOOMGENERIC_RESX; col++) {
294+
if (gamma_correct_enabled) {
295+
pixel->r = byte_sqrt[pixel->r];
296+
pixel->g = byte_sqrt[pixel->g];
297+
pixel->b = byte_sqrt[pixel->b];
298+
}
299+
249300
if (color_enabled && (color ^ *(uint32_t *)pixel) & 0x00FFFFFF) {
250301
BUF_PUTS(buf, "\033[38;2;");
251302
BUF_ITOA(buf, pixel->r);
@@ -257,11 +308,24 @@ void DG_DrawFrame(void)
257308
color = *(uint32_t *)pixel;
258309
}
259310

260-
if (unicode_enabled) {
311+
switch (character_set) {
312+
case ASCII:
313+
if (gradient_enabled) {
314+
const char v_char = grad[(pixel->r + pixel->g + pixel->b)
315+
* GRAD_LEN / RGB_SUM_MAX];
316+
BUF_PUTCHAR(buf, v_char);
317+
BUF_PUTCHAR(buf, v_char);
318+
} else {
319+
BUF_PUTS(buf, "##");
320+
}
321+
break;
322+
case BLOCK:
261323
if (gradient_enabled) {
262-
const size_t idx = (pixel->r + pixel->g + pixel->b) * (UNICODE_GRAD_LEN + 1U) / RGB_SUM_MAX;
324+
const size_t idx = (pixel->r + pixel->g + pixel->b)
325+
* (UNICODE_GRAD_LEN + 1U) / RGB_SUM_MAX;
263326
if (idx) {
264-
const void *const v_char = &unicode_grad[(idx - 1) * 3];
327+
const void *const v_char =
328+
&unicode_grad[(idx - 1) * 3];
265329
BUF_MEMCPY(buf, v_char, 3);
266330
BUF_MEMCPY(buf, v_char, 3);
267331
} else {
@@ -270,14 +334,24 @@ void DG_DrawFrame(void)
270334
} else {
271335
BUF_PUTS(buf, "\u2588\u2588");
272336
}
273-
} else {
337+
break;
338+
case BRAILLE:
274339
if (gradient_enabled) {
275-
const char v_char = grad[(pixel->r + pixel->g + pixel->b) * GRAD_LEN / RGB_SUM_MAX];
276-
BUF_PUTCHAR(buf, v_char);
277-
BUF_PUTCHAR(buf, v_char);
340+
const size_t idx =
341+
(pixel->r + pixel->g + pixel->b) * 8 / RGB_SUM_MAX;
342+
if (idx) {
343+
const char *const gradient = braille_grads[idx - 1];
344+
const size_t len =
345+
braille_grad_lengths[idx - 1] / 3;
346+
BUF_MEMCPY(buf, &gradient[(random() % len) * 3], 3);
347+
BUF_MEMCPY(buf, &gradient[(random() % len) * 3], 3);
348+
} else {
349+
BUF_PUTS(buf, " ");
350+
}
278351
} else {
279-
BUF_PUTS(buf, "##");
352+
BUF_PUTS(buf, "\u28ff\u28ff");
280353
}
354+
break;
281355
}
282356

283357
pixel++;
@@ -540,12 +614,16 @@ void DG_ReadInput(void)
540614
unsigned input_count = 0;
541615
if (event_cnt) {
542616
INPUT_RECORD input_records[32];
543-
WINDOWS_CALL(!ReadConsoleInput(hInputHandle, input_records, 32, &event_cnt), "DG_ReadInput: %s");
617+
WINDOWS_CALL(!ReadConsoleInput(hInputHandle, input_records, 32, &event_cnt),
618+
"DG_ReadInput: %s");
544619

545620
DWORD i;
546621
for (i = 0; i < event_cnt; i++) {
547-
if (input_records[i].Event.KeyEvent.bKeyDown && input_records[i].EventType == KEY_EVENT) {
548-
unsigned char inp = convertToDoomKey(input_records[i].Event.KeyEvent.wVirtualKeyCode, input_records[i].Event.KeyEvent.uChar.AsciiChar);
622+
if (input_records[i].Event.KeyEvent.bKeyDown
623+
&& input_records[i].EventType == KEY_EVENT) {
624+
unsigned char inp = convertToDoomKey(
625+
input_records[i].Event.KeyEvent.wVirtualKeyCode,
626+
input_records[i].Event.KeyEvent.uChar.AsciiChar);
549627
if (inp) {
550628
input_buffer[input_count++] = inp;
551629
if (input_count == INPUT_BUFFER_LEN - 1u)

0 commit comments

Comments
 (0)