diff --git a/CHANGES b/CHANGES index 2008d10..462eb8e 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,9 @@ - New - Show spinning animation at the beginning of -l / --live output line, visibility configurable using LiveSpinner configuration option + - Add --invert-colors option to image output for facilitating for example + dark mode switching without needing to have multiple separate color + configurations 2.11 / 19-Aug-2023 diff --git a/man/vnstati.1 b/man/vnstati.1 index 2b498a2..cf0cfe8 100644 --- a/man/vnstati.1 +++ b/man/vnstati.1 @@ -1,4 +1,4 @@ -.TH VNSTATI 1 "AUGUST 2023" "version 2.11" "User Manuals" +.TH VNSTATI 1 "SEPTEMBER 2023" "version 2.12" "User Manuals" .SH NAME vnstati \- image output support for vnStat @@ -45,6 +45,8 @@ vnstati \- image output support for vnStat .IR interface ] .RB [ \-\-iface .IR interface ] +.RB [ \-\-invert\-colors +.RI [ mode ]] .RB [ \-\-large ] .RB [ \-\-limit .IR limit ] @@ -251,6 +253,16 @@ option is optional and can be used as parameter on the command line for selecting the used interface even without the option being explicitly used. +.TP +.BI "--invert-colors " [mode] +Invert image colors. Results in black becoming white, dark colors becoming light, +light colors becoming dark and white becoming black. The optional +.I mode +parameter can be used to change the color inversion behaviour. Available modes: +0 = no color inversion, 1 = invert all colors except those used for rx and tx, +2 = invert all colors. When +.I mode +isn't specific, mode 1 will be used. .TP .B "-L, --large" diff --git a/src/image.c b/src/image.c index 9dd810f..c209560 100644 --- a/src/image.c +++ b/src/image.c @@ -11,6 +11,7 @@ void initimagecontent(IMAGECONTENT *ic) ic->font = gdFontGetSmall(); ic->lineheight = 12; ic->large = 0; + ic->invert = 0; ic->showheader = 1; ic->showedge = 1; ic->showlegend = 1; diff --git a/src/image.h b/src/image.h index 89d37c3..7d161e9 100644 --- a/src/image.h +++ b/src/image.h @@ -24,7 +24,7 @@ typedef struct { interfaceinfo interface; int cbackground, cedge, cheader, cheadertitle, cheaderdate, ctext, cline, clinel, cvnstat; int crx, crxd, ctx, ctxd, cbgoffset, cbgoffsetmore, showheader, showedge, showlegend, altdate; - int lineheight, large; + int lineheight, large, invert; char headertext[65], databegin[18], dataend[18]; time_t current; } IMAGECONTENT; diff --git a/src/image_support.c b/src/image_support.c index 55201f0..eb23297 100644 --- a/src/image_support.c +++ b/src/image_support.c @@ -7,75 +7,93 @@ void imageinit(IMAGECONTENT *ic, const int width, const int height) { - int rgb[3]; + int rgb[3], invert = 1; ic->im = gdImageCreate(width, height); + if (ic->invert > 0) { + invert = -1; + } + /* text, edge and header colors */ hextorgb(cfg.ctext, rgb); + if (ic->invert > 0) { invertcolor(rgb); } ic->ctext = gdImageColorAllocate(ic->im, rgb[0], rgb[1], rgb[2]); colorinitcheck("ctext", ic->ctext, cfg.ctext, rgb); hextorgb(cfg.cedge, rgb); + if (ic->invert > 0) { invertcolor(rgb); } ic->cedge = gdImageColorAllocate(ic->im, rgb[0], rgb[1], rgb[2]); colorinitcheck("cedge", ic->cedge, cfg.cedge, rgb); hextorgb(cfg.cheader, rgb); + if (ic->invert > 0) { invertcolor(rgb); } ic->cheader = gdImageColorAllocate(ic->im, rgb[0], rgb[1], rgb[2]); colorinitcheck("cheader", ic->cheader, cfg.cheader, rgb); hextorgb(cfg.cheadertitle, rgb); + if (ic->invert > 0) { invertcolor(rgb); } ic->cheadertitle = gdImageColorAllocate(ic->im, rgb[0], rgb[1], rgb[2]); colorinitcheck("cheadertitle", ic->cheadertitle, cfg.cheadertitle, rgb); hextorgb(cfg.cheaderdate, rgb); + if (ic->invert > 0) { invertcolor(rgb); } ic->cheaderdate = gdImageColorAllocate(ic->im, rgb[0], rgb[1], rgb[2]); colorinitcheck("cheaderdate", ic->cheaderdate, cfg.cheaderdate, rgb); /* lines */ hextorgb(cfg.cline, rgb); + if (ic->invert > 0) { invertcolor(rgb); } ic->cline = gdImageColorAllocate(ic->im, rgb[0], rgb[1], rgb[2]); colorinitcheck("cline", ic->cline, cfg.cline, rgb); if (cfg.clinel[0] == '-') { - modcolor(rgb, 50, 1); + modcolor(rgb, 50 * invert, 1); } else { hextorgb(cfg.clinel, rgb); + if (ic->invert > 0) { invertcolor(rgb); } } ic->clinel = gdImageColorAllocate(ic->im, rgb[0], rgb[1], rgb[2]); colorinitcheck("clinel", ic->clinel, cfg.clinel, rgb); /* background */ hextorgb(cfg.cbg, rgb); + if (ic->invert > 0) { invertcolor(rgb); } ic->cbackground = gdImageColorAllocate(ic->im, rgb[0], rgb[1], rgb[2]); colorinitcheck("cbackground", ic->cbackground, cfg.cbg, rgb); - modcolor(rgb, -35, 0); + modcolor(rgb, -35 * invert, 0); ic->cvnstat = gdImageColorAllocate(ic->im, rgb[0], rgb[1], rgb[2]); colorinitcheck("cvnstat", ic->cvnstat, cfg.cbg, rgb); hextorgb(cfg.cbg, rgb); - modcolor(rgb, -15, 0); + if (ic->invert > 0) { invertcolor(rgb); } + modcolor(rgb, -15 * invert, 0); ic->cbgoffset = gdImageColorAllocate(ic->im, rgb[0], rgb[1], rgb[2]); colorinitcheck("cbgoffset", ic->cbgoffset, cfg.cbg, rgb); hextorgb(cfg.cbg, rgb); - modcolor(rgb, -40, 0); + if (ic->invert > 0) { invertcolor(rgb); } + modcolor(rgb, -40 * invert, 0); ic->cbgoffsetmore = gdImageColorAllocate(ic->im, rgb[0], rgb[1], rgb[2]); colorinitcheck("cbgoffsetmore", ic->cbgoffsetmore, cfg.cbg, rgb); /* rx */ hextorgb(cfg.crx, rgb); + if (ic->invert > 1) { invertcolor(rgb); } ic->crx = gdImageColorAllocate(ic->im, rgb[0], rgb[1], rgb[2]); colorinitcheck("crx", ic->crx, cfg.crx, rgb); if (cfg.crxd[0] == '-') { - modcolor(rgb, -50, 1); + modcolor(rgb, -50 * invert, 1); } else { hextorgb(cfg.crxd, rgb); + if (ic->invert > 1) { invertcolor(rgb); } } ic->crxd = gdImageColorAllocate(ic->im, rgb[0], rgb[1], rgb[2]); colorinitcheck("crxd", ic->crxd, cfg.crxd, rgb); /* tx */ hextorgb(cfg.ctx, rgb); + if (ic->invert > 1) { invertcolor(rgb); } ic->ctx = gdImageColorAllocate(ic->im, rgb[0], rgb[1], rgb[2]); colorinitcheck("ctx", ic->ctx, cfg.ctx, rgb); if (cfg.ctxd[0] == '-') { - modcolor(rgb, -50, 1); + modcolor(rgb, -50 * invert, 1); } else { hextorgb(cfg.ctxd, rgb); + if (ic->invert > 1) { invertcolor(rgb); } } ic->ctxd = gdImageColorAllocate(ic->im, rgb[0], rgb[1], rgb[2]); colorinitcheck("ctxd", ic->ctxd, cfg.ctxd, rgb); @@ -429,6 +447,23 @@ void modcolor(int *rgb, const int offset, const int force) } } +void invertcolor(int *rgb) +{ + int i; + + if (debug) { + printf("invert: %d, %d, %d -> ", rgb[0], rgb[1], rgb[2]); + } + + for (i = 0; i < 3; i++) { + rgb[i] = 255 - rgb[i]; + } + + if (debug) { + printf("%d, %d, %d\n", rgb[0], rgb[1], rgb[2]); + } +} + char *getimagevalue(const uint64_t b, const int len, const int rate) { static char buffer[64]; diff --git a/src/image_support.h b/src/image_support.h index 9a97d79..c1603b7 100644 --- a/src/image_support.h +++ b/src/image_support.h @@ -17,6 +17,7 @@ void drawarrowup(IMAGECONTENT *ic, const int x, const int y); void drawarrowright(IMAGECONTENT *ic, const int x, const int y); void hextorgb(const char *input, int *rgb); void modcolor(int *rgb, const int offset, const int force); +void invertcolor(int *rgb); char *getimagevalue(const uint64_t b, const int len, const int rate); char *getimagescale(const uint64_t b, const int rate); uint64_t getscale(const uint64_t input, const int rate); diff --git a/src/vnstati.c b/src/vnstati.c index 6b4ae68..4d9de97 100644 --- a/src/vnstati.c +++ b/src/vnstati.c @@ -165,7 +165,8 @@ void showihelp(const IPARAMS *p) #if HAVE_DECL_GD_NEAREST_NEIGHBOUR printf(" --scale change image size by scaling it\n"); #endif - printf(" --transparent [enabled] toggle background transparency\n\n"); + printf(" --transparent [enabled] toggle background transparency\n"); + printf(" --invert-colors invert image colors (0-2)\n\n"); printf("See also \"man vnstati\".\n"); } @@ -502,6 +503,26 @@ void parseargs(IPARAMS *p, IMAGECONTENT *ic, int argc, char **argv) fprintf(stderr, "Error: Invalid or missing parameter for %s.\n", argv[currentarg]); exit(EXIT_FAILURE); } + } else if (strcmp(argv[currentarg], "--invert-colors") == 0) { + if (currentarg + 1 < argc && (strlen(argv[currentarg + 1]) == 1 || ishelprequest(argv[currentarg + 1]))) { + if (!isdigit(argv[currentarg + 1][0]) || atoi(argv[currentarg + 1]) > 2 || atoi(argv[currentarg + 1]) < 0) { + if (!ishelprequest(argv[currentarg + 1])) + fprintf(stderr, "Error: Invalid parameter \"%s\".\n", argv[currentarg + 1]); + printf(" Valid parameters for %s:\n", argv[currentarg]); + printf(" 0 - no color inversion\n"); + printf(" 1 - invert all colors except rx and tx\n"); + printf(" 2 - invert all colors\n"); + exit(EXIT_FAILURE); + } + ic->invert = atoi(argv[currentarg + 1]); + if (debug) + printf("Invert colors changed: %d\n", ic->invert); + currentarg++; + } else { + ic->invert = !ic->invert; + if (debug) + printf("Invert colors changed: %d\n", ic->invert); + } } else if ((strcmp(argv[currentarg], "-v") == 0) || (strcmp(argv[currentarg], "--version")) == 0) { printf("vnStat image output %s by Teemu Toivola (SQLite %s, LibGD %d.%d.%d)\n", getversion(), sqlite3_libversion(), GD_MAJOR_VERSION, GD_MINOR_VERSION, GD_RELEASE_VERSION); exit(EXIT_SUCCESS); diff --git a/tests/image_tests.c b/tests/image_tests.c index 083c142..d8121cc 100644 --- a/tests/image_tests.c +++ b/tests/image_tests.c @@ -871,6 +871,31 @@ START_TEST(modcolor_mods_colors) } END_TEST +START_TEST(invertcolor_inverts_colors) +{ + int rgb[3]; + + debug = 1; + suppress_output(); + + rgb[0] = 0; + rgb[1] = 10; + rgb[2] = 255; + invertcolor(rgb); + ck_assert_int_eq(rgb[0], 255); + ck_assert_int_eq(rgb[1], 245); + ck_assert_int_eq(rgb[2], 0); + + rgb[0] = 50; + rgb[1] = 150; + rgb[2] = 200; + invertcolor(rgb); + ck_assert_int_eq(rgb[0], 205); + ck_assert_int_eq(rgb[1], 105); + ck_assert_int_eq(rgb[2], 55); +} +END_TEST + void add_image_tests(Suite *s) { TCase *tc_image = tcase_create("Image"); @@ -897,5 +922,6 @@ void add_image_tests(Suite *s) tcase_add_test(tc_image, element_output_check); tcase_add_test(tc_image, hextorgb_can_convert); tcase_add_test(tc_image, modcolor_mods_colors); + tcase_add_test(tc_image, invertcolor_inverts_colors); suite_add_tcase(s, tc_image); }