]> git.rm.cloudns.org Git - xonotic/darkplaces.git/commitdiff
con: implement 24-bit RGB terminal output
authorbones_was_here <bones_was_here@xonotic.au>
Thu, 15 Aug 2024 14:11:22 +0000 (00:11 +1000)
committerbones_was_here <bones_was_here@xonotic.au>
Sat, 14 Sep 2024 08:17:31 +0000 (18:17 +1000)
Mode 2 behaves like the familiar mode 1 for best compatibility with
terminal themes and expectations.
Mode 3 behaves like the ingame console.

2 is now the default on Linux.

Moves the r_text* cvar change from
d987323e8a9bcc3102c96a237f74875d5b37c4e4 to a better place, and
documents it.

Signed-off-by: bones_was_here <bones_was_here@xonotic.au>
cmd.c
console.c
draw.h
gl_draw.c

diff --git a/cmd.c b/cmd.c
index cbb3fecaa49b7bd623ca1799006d2e9125522229..ec1c6baf480651c77a5c5dc06a055250bf062165 100644 (file)
--- a/cmd.c
+++ b/cmd.c
@@ -666,6 +666,9 @@ static void Cmd_Exec(cmd_state_t *cmd, const char *filename)
 "sv_gameplayfix_q1bsptracelinereportstexture 0\n"
 "sv_gameplayfix_swiminbmodels 0\n"
 "sv_gameplayfix_downtracesupportsongroundflag 0\n"
+// Work around low brightness and poor legibility of Quake font
+"r_textbrightness 0.25\n"
+"r_textcontrast 1.25\n"
                                );
                        break;
                default:
index f46eddb5fb8bab5ce1863613f378dfab85eb24a6..180f895796f108dff827c014a66f47aebf95fdc1 100644 (file)
--- a/console.c
+++ b/console.c
@@ -63,11 +63,7 @@ cvar_t con_chatsound_team_file = {CF_CLIENT, "con_chatsound_team_file","sound/mi
 cvar_t con_chatsound_team_mask = {CF_CLIENT, "con_chatsound_team_mask","40","Magic ASCII code that denotes a team chat message"};
 
 cvar_t sys_specialcharactertranslation = {CF_CLIENT | CF_SERVER, "sys_specialcharactertranslation", "1", "terminal console conchars to ASCII translation (set to 0 if your conchars.tga is for an 8bit character set or if you want raw output)"};
-#ifdef WIN32
-cvar_t sys_colortranslation = {CF_CLIENT | CF_SERVER, "sys_colortranslation", "0", "terminal console color translation (supported values: 0 = strip color codes, 1 = translate to ANSI codes, 2 = no translation)"};
-#else
-cvar_t sys_colortranslation = {CF_CLIENT | CF_SERVER, "sys_colortranslation", "1", "terminal console color translation (supported values: 0 = strip color codes, 1 = translate to ANSI codes, 2 = no translation)"};
-#endif
+cvar_t sys_colortranslation = {CF_CLIENT | CF_SERVER, "sys_colortranslation", "1", "terminal console color translation (supported values: -1 = print codes without translation, 0 = strip color codes, 1 = translate to ANSI codes, 2 = translate DP RGB to 24-bit and Quake colors to ANSI, 3 = translate all colors to 24-bit RGB)"};
 
 
 cvar_t con_nickcompletion = {CF_CLIENT | CF_ARCHIVE, "con_nickcompletion", "1", "tab-complete nicks in console and message input"};
@@ -876,6 +872,15 @@ void Con_Init (void)
 
        Cvar_RegisterVariable (&sys_colortranslation);
        Cvar_RegisterVariable (&sys_specialcharactertranslation);
+#if defined(__linux__)
+       // Linux terminals natively support RGB 8bpc codes or convert them to a palette.
+       Cvar_SetQuick(&sys_colortranslation, "2");
+#elif defined(WIN32)
+       // Windows 10 default PowerShell and cmd.exe have no RGB or ANSI support by default.
+       // TODO: it can be enabled on current versions using a platform-specific call,
+       // issue: https://gitlab.com/xonotic/darkplaces/-/issues/426
+       Cvar_SetQuick(&sys_colortranslation, "0");
+#endif
 
        Cvar_RegisterVariable (&log_file);
        Cvar_RegisterVariable (&log_file_stripcolors);
@@ -1151,6 +1156,8 @@ Con_MaskPrint
 */
 extern cvar_t timestamps;
 extern cvar_t timeformat;
+extern cvar_t r_textcontrast;
+extern cvar_t r_textbrightness;
 void Con_MaskPrint(unsigned additionalmask, const char *msg)
 {
        static unsigned mask = 0;
@@ -1205,6 +1212,7 @@ void Con_MaskPrint(unsigned additionalmask, const char *msg)
                // append the character
                line[index++] = *msg;
                // if this is a newline character, we have a complete line to print
+               // bones_was_here: why do we only use half the line buffer?
                if (*msg == '\n' || index >= (int)sizeof(line) / 2)
                {
                        // terminate the line
@@ -1240,15 +1248,20 @@ void Con_MaskPrint(unsigned additionalmask, const char *msg)
                                        }
                                }
 
-                               if(sys_colortranslation.integer == 1) // ANSI
+                               if(sys_colortranslation.integer > 0) // ANSI, RGB, or both
                                {
-                                       static char printline[MAX_INPUTLINE * 4 + 3];
+                                       // ANSI translation:
                                                // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
                                                // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
+                                       // 8bpc RGB brings new worst-cases:
+                                               // 5 can become 21 bytes, rounding that up to * 5, plenty of space for extra bytes at the end.
+                                               // sys_colortranslation 3: 2 can become 21 bytes, rounding that up to * 11
+                                       char printline[sizeof(line) * 11];
                                        int lastcolor = 0;
                                        const char *in;
                                        char *out;
                                        int color;
+                                       u8 rgb[3];
                                        for(in = line, out = printline; *in; ++in)
                                        {
                                                switch(*in)
@@ -1256,23 +1269,66 @@ void Con_MaskPrint(unsigned additionalmask, const char *msg)
                                                        case STRING_COLOR_TAG:
                                                                if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
                                                                {
-                                                                       char r = tolower(in[2]);
-                                                                       char g = tolower(in[3]);
-                                                                       char b = tolower(in[4]);
+                                                                       VectorCopy(in + 2, rgb);
                                                                        // it's a hex digit already, so the else part needs no check --blub
-                                                                       if(isdigit(r)) r -= '0';
-                                                                       else r -= 87;
-                                                                       if(isdigit(g)) g -= '0';
-                                                                       else g -= 87;
-                                                                       if(isdigit(b)) b -= '0';
-                                                                       else b -= 87;
+                                                                       for (int i = 0; i < 3; ++i)
+                                                                       {
+                                                                               if (isdigit(rgb[i])) rgb[i] -= '0';
+                                                                               else rgb[i] = tolower(rgb[i]) - 87;
+                                                                               rgb[i] *= 17;
+                                                                       }
+
+                                                                       if (sys_colortranslation.integer > 1) // 8bpc RGB
+                                                                       {
+                                                                               char *p;
+                                                                               float B;
+
+                                                                               in += 4;
+                                                                       rgbout:
+                                                                               color = rgb[0]<<16 | rgb[1]<<8 | rgb[2] | /* disambiguates from quake colours */ 0x40000000;
+                                                                               if (lastcolor == color)
+                                                                                       break;
+                                                                               else
+                                                                                       lastcolor = color;
+
+                                                                               B = r_textbrightness.value * 255;
+                                                                               for (int i = 0; i < 3; ++i)
+                                                                                       rgb[i] = bound(0, rgb[i] * r_textcontrast.value + B, 255);
+
+                                                                               // format must be decimal 0-255, max length is 21 bytes
+                                                                               if (sys_colortranslation.integer == 2)
+                                                                                       memcpy(out, "\x1B[1;38;2", 8);
+                                                                               else
+                                                                                       memcpy(out, "\x1B[0;38;2", 8);
+                                                                               out += 8;
+                                                                               for (int i = 0; i < 3; ++i)
+                                                                               {
+                                                                                       *out++ = ';';
+                                                                                       p = out += ((rgb[i] > 99) ? 3 : (rgb[i] > 9) ? 2 : 1);
+                                                                                       do { *--p = (rgb[i] % 10) + '0';
+                                                                                       } while ((rgb[i] /= 10) > 0);
+                                                                               }
+                                                                               *out++ = 'm';
+
+                                                                               break;
+                                                                       }
                                                                        
-                                                                       color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
+                                                                       color = Sys_Con_NearestColor(rgb[0], rgb[1], rgb[2]);
                                                                        in += 3; // 3 only, the switch down there does the fourth
                                                                }
                                                                else
+                                                               {
                                                                        color = in[1];
                                                                
+                                                                       if (sys_colortranslation.integer == 3 && isdigit(color)) // Quake to RGB
+                                                                       {
+                                                                               color -= '0';
+                                                                               VectorScale(string_colors[color], 255 * string_colors[color][3], rgb);
+                                                                               ++in;
+                                                                               goto rgbout;
+                                                                       }
+                                                               }
+
                                                                switch(color)
                                                                {
                                                                        case STRING_COLOR_TAG:
@@ -1357,13 +1413,13 @@ void Con_MaskPrint(unsigned additionalmask, const char *msg)
                                        *out = '\0';
                                        Sys_Print(printline, out - printline);
                                }
-                               else if(sys_colortranslation.integer == 2) // Quake
+                               else if(sys_colortranslation.integer == -1) // print as text
                                {
                                        Sys_Print(line, index);
                                }
                                else // strip
                                {
-                                       static char printline[MAX_INPUTLINE]; // it can only get shorter here
+                                       char printline[MAX_INPUTLINE]; // it can only get shorter here
                                        const char *in;
                                        char *out;
                                        for(in = line, out = printline; *in; ++in)
diff --git a/draw.h b/draw.h
index 8496bb73a1cf1e1dd67a15e0972419c55cf7d7f3..76b24e0de283b03d48b2c81ae95b206f0398ab40 100644 (file)
--- a/draw.h
+++ b/draw.h
@@ -159,6 +159,7 @@ void DrawQ_Pic(float x, float y, cachepic_t *pic, float width, float height, flo
 void DrawQ_RotPic(float x, float y, cachepic_t *pic, float width, float height, float org_x, float org_y, float angle, float red, float green, float blue, float alpha, int flags);
 // draw a filled rectangle (slightly faster than DrawQ_Pic with pic = NULL)
 void DrawQ_Fill(float x, float y, float width, float height, float red, float green, float blue, float alpha, int flags);
+
 // draw a text string,
 // with optional color tag support,
 // returns final unclipped x coordinate
@@ -166,12 +167,14 @@ void DrawQ_Fill(float x, float y, float width, float height, float red, float gr
 // the color is tinted by the provided base color
 // if r_textshadow is not zero, an additional instance of the text is drawn first at an offset with an inverted shade of gray (black text produces a white shadow, brightly colored text produces a black shadow)
 extern float DrawQ_Color[4];
+extern const vec4_t string_colors[];
 float DrawQ_String(float x, float y, const char *text, size_t maxlen, float scalex, float scaley, float basered, float basegreen, float baseblue, float basealpha, int flags, int *outcolor, qbool ignorecolorcodes, const dp_font_t *fnt);
 float DrawQ_String_Scale(float x, float y, const char *text, size_t maxlen, float sizex, float sizey, float scalex, float scaley, float basered, float basegreen, float baseblue, float basealpha, int flags, int *outcolor, qbool ignorecolorcodes, const dp_font_t *fnt);
 float DrawQ_TextWidth(const char *text, size_t maxlen, float w, float h, qbool ignorecolorcodes, const dp_font_t *fnt);
 float DrawQ_TextWidth_UntilWidth(const char *text, size_t *maxlen, float w, float h, qbool ignorecolorcodes, const dp_font_t *fnt, float maxWidth);
 float DrawQ_TextWidth_UntilWidth_TrackColors(const char *text, size_t *maxlen, float w, float h, int *outcolor, qbool ignorecolorcodes, const dp_font_t *fnt, float maxwidth);
 float DrawQ_TextWidth_UntilWidth_TrackColors_Scale(const char *text, size_t *maxlen, float w, float h, float sw, float sh, int *outcolor, qbool ignorecolorcodes, const dp_font_t *fnt, float maxwidth);
+
 // draw a very fancy pic (per corner texcoord/color control), the order is tl, tr, bl, br
 void DrawQ_SuperPic(float x, float y, cachepic_t *pic, float width, float height, float s1, float t1, float r1, float g1, float b1, float a1, float s2, float t2, float r2, float g2, float b2, float a2, float s3, float t3, float r3, float g3, float b3, float a3, float s4, float t4, float r4, float g4, float b4, float a4, int flags);
 // set the clipping area
index 3eb4c131c9b2dce2c27089634d7bb2bef4aec17a..11a080afeca447cde15b127a7f67d450bcb89140 100644 (file)
--- a/gl_draw.c
+++ b/gl_draw.c
@@ -51,8 +51,9 @@ dp_fonts_t dp_fonts;
 static mempool_t *fonts_mempool = NULL;
 
 cvar_t r_textshadow = {CF_CLIENT | CF_ARCHIVE, "r_textshadow", "0", "draws a shadow on all text to improve readability (note: value controls offset, 1 = 1 pixel, 1.5 = 1.5 pixels, etc)"};
-cvar_t r_textbrightness = {CF_CLIENT | CF_ARCHIVE, "r_textbrightness", "0.25", "additional brightness for text color codes (0 keeps colors as is, 1 makes them all white)"};
-cvar_t r_textcontrast = {CF_CLIENT | CF_ARCHIVE, "r_textcontrast", "1.25", "additional contrast for text color codes (1 keeps colors as is, 0 makes them all black)"};
+// these are also read by the dedicated server when sys_colortranslation > 1
+cvar_t r_textbrightness = {CF_SHARED | CF_ARCHIVE, "r_textbrightness", "0", "additional brightness for text color codes (0 keeps colors as is, 1 makes them all white)"};
+cvar_t r_textcontrast = {CF_SHARED | CF_ARCHIVE, "r_textcontrast", "1", "additional contrast for text color codes (1 keeps colors as is, 0 makes them all black)"};
 
 cvar_t r_font_postprocess_blur = {CF_CLIENT | CF_ARCHIVE, "r_font_postprocess_blur", "0", "font blur amount"};
 cvar_t r_font_postprocess_outline = {CF_CLIENT | CF_ARCHIVE, "r_font_postprocess_outline", "0", "font outline amount"};
@@ -849,7 +850,7 @@ void DrawQ_Fill(float x, float y, float width, float height, float red, float gr
 }
 
 /// color tag printing
-static const vec4_t string_colors[] =
+const vec4_t string_colors[] =
 {
        // Quake3 colors
        // LadyHavoc: why on earth is cyan before magenta in Quake3?