fltk/test/contrast.cxx
Albrecht Schlosser 9820d9f555 Fix and improve fl_contrast() calculations
- src/fl_contrast.cxx:
  - FL_CONTRAST_LEGACY: reset black/white threshold from 55 to 50
  - FL_CONTRAST_CIELAB: change default contrast level from 55 to 39

- test/contrast.cxx:
  - set default contrast mode to CIELAB
  - add "random color" button to test random foreground colors (text)
  - slightly increase terminal size
  - make the Fl_Terminal widget the resizable() of the window
2024-05-16 19:58:24 +02:00

495 lines
15 KiB
C++

//
// Contrast function test program for the Fast Light Tool Kit (FLTK).
//
// Copyright 2022-2024 by Bill Spitzak and others.
//
// This library is free software. Distribution and use rights are outlined in
// the file "COPYING" which should have been included with this file. If this
// file is missing or damaged, see the license at:
//
// https://www.fltk.org/COPYING.php
//
// Please see the following page on how to report bugs and issues:
//
// https://www.fltk.org/bugs.php
//
// Note: this test and demo program is work in progress. It is published
// because it is helpful but it needs some more work to be "perfect" ;-)
// AlbrechtS
#include <FL/Fl.H>
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Radio_Round_Button.H>
#include <FL/Fl_Color_Chooser.H>
#include <FL/Fl_Output.H>
#include <FL/Fl_Hor_Value_Slider.H>
#include <FL/Fl_Terminal.H>
#include <FL/fl_draw.H>
#include <math.h>
#include <stdlib.h>
#include <string.h>
// program version
const char *version = "0.9.1";
// prototypes and forward declarations
static void button_cb(Fl_Widget *w, void *v);
static Fl_Color calc_contrast(Fl_Color fg, Fl_Color bg, Fl_Fontsize fs);
// class Button
class Button : public Fl_Button {
char lbuf[20]; // private label buffer
Fl_Color ocol_; // "original" (label) color
int idx_; // button index (0 - 255)
public:
Button(int X, int Y, int W, int H, int n)
: Fl_Button(X, Y, W, H, "") {
idx_ = n;
box(FL_THIN_DOWN_BOX);
label(lbuf);
callback(button_cb);
color((Fl_Color)n);
sprintf(lbuf, "%03d", n);
set_labelcolor(n);
labelsize(15);
labelfont(FL_HELVETICA);
}
void set_labelcolor(Fl_Color col) {
ocol_ = col;
labelcolor(calc_contrast(col, color(), labelsize()));
}
Fl_Color ocol() {
return ocol_;
}
int idx() {
return idx_;
}
void draw() FL_OVERRIDE {
draw_box();
// draw small filled rectangle with "original" color
fl_color(ocol_);
fl_rectf(x() + 5, y() + 5, 10, h() - 10);
// measure and draw label
int lw = 0, lh = 0;
fl_font(labelfont(), labelsize());
fl_measure(lbuf, lw, lh);
fl_color(labelcolor());
fl_draw(lbuf, x() + 15 + (w() - lw - 15) / 2, y() + h() - (h() - lh) / 2 - lh/4);
fl_color(FL_BLACK);
}
}; // class Button
// global variables
Fl_Terminal *term = 0;
double g_lfg; // perceived lightness of foreground color
double g_lbg; // perceived lightness of background color
double g_lcref; // calculated contrast reference (CIELAB, L*a*b*)
int g_selected = -1; // selected button: -1 = none, 0 - 255 = valid button
Fl_Fontsize g_fs = 15; // fontsize for button labels
int g_level = 0; // *init* fl_contrast_level (sensitivity)
int g_algo = FL_CONTRAST_CIELAB; // contrast algorithm: 0 = none, 1 = legacy (1.3.x), 2 = CIELAB, 3 = custom
const char *alch = ""; // algorithm as char: "LEGACY", "CIELAB" , or "CUSTOM"
Fl_Color lcolor = FL_BLACK; // label color, set by slider callback
Button *buttons[256]; // array of color buttons
Fl_Value_Slider *sliders[6]; // array of sliders (gray, red, green, blue, level, fontsize)
Fl_Output *color_out = 0; // color output (RRGGBB)
// Custom contrast algorithm: currently a dummy function (returns fg).
// This may be used to define a "better" contrast function in user code
static Fl_Color custom_contrast(Fl_Color fg, Fl_Color bg, Fl_Fontsize fs, int) {
return fg;
}
/*
Local function to calculate the contrast and store it in some
global variables for display purposes and logging.
This function is a wrapper around fl_contrast() in this demo program.
*/
static Fl_Color calc_contrast(Fl_Color fg, Fl_Color bg, Fl_Fontsize fs) {
// Compute and set global *perceived* lightness L* (Lstar) and contrast for display
g_lfg = fl_lightness(fg);
g_lbg = fl_lightness(bg);
g_lcref = g_lfg - g_lbg; // perceived contrast (light on dark = positive)
switch (g_algo) {
case FL_CONTRAST_NONE: // 0 = none (return fg)
case FL_CONTRAST_LEGACY: // 1 = legacy (FLTK 1.3.x)
case FL_CONTRAST_CIELAB: // 2 = CIELAB (L*a*b*)
case FL_CONTRAST_CUSTOM: // 3 = Custom
return fl_contrast(fg, bg, fs);
default:
break;
}
return fg;
}
// set all button label colors and adjust fontsize (labelsize)
static void update_labels() {
for (int i = 0; i < 256; i++) {
buttons[i]->set_labelcolor(lcolor);
buttons[i]->labelsize(g_fs);
}
}
static void button_cb(Fl_Widget *w, void *v) {
Button *b = (Button *)w;
g_selected = b->idx(); // selected button index
Fl_Color ocol = Fl::get_color(b->ocol()); // button's "original" label color (RGB0)
Fl_Color fg = Fl::get_color(b->labelcolor()); // button's label color (RGB0)
Fl_Color bg = Fl::get_color(b->color()); // button's background color (RGB0)
calc_contrast(ocol, bg, g_fs); // calculate values to be displayed
const char *color = ""; // calculated label color (text)
if (fg == ocol) color = "fg";
else if (fg == 0xffffff00) color = "WHITE";
else if (fg == 0x0) color = "BLACK";
term->printf("[%s] fg: %06x, bg: %06x, lfg: %6.2f, lbg: %6.2f, lc: %7.2f, %s => %-5s",
b->label(), ocol >> 8, bg >> 8, g_lfg, g_lbg, g_lcref, alch, color);
if (g_algo == FL_CONTRAST_LEGACY ||
g_algo == FL_CONTRAST_CIELAB)
term->printf(" (level = %3d)\n", g_level);
else
term->printf("\n");
}
void lf_cb(Fl_Widget *w, void *v) {
term->printf("\n");
}
// callback for color (gray and R, G, B) sliders
void color_slider_cb(Fl_Widget *w, void *v) {
int n = fl_int(v); // slider type: 0 = gray, 1 = color
unsigned int r, g, b;
if (n == 0) { // gray slider
int val = (int)sliders[0]->value();
lcolor = fl_rgb_color(val, val, val); // set gray value
sliders[1]->value(val); // set r/g/b values as well
sliders[2]->value(val);
sliders[3]->value(val);
r = g = b = val;
} else { // any color slider
r = (unsigned int)sliders[1]->value();
g = (unsigned int)sliders[2]->value();
b = (unsigned int)sliders[3]->value();
lcolor = fl_rgb_color(r, g, b); // set color value
}
// update button label colors
update_labels();
// output label color
char color_buf[10];
sprintf(color_buf, "%02X %02X %02X", r, g, b);
color_out->value(color_buf);
w->window()->redraw();
}
// callback for "level" and "fontsize" sliders
void slider_cb(Fl_Widget *w, void *v) {
int n = fl_int(v); // slider type: 1 = level, 2 = fontsize
switch (n) {
case 1: // fl_contrast_level()
g_level = (int)sliders[n + 3]->value();
fl_contrast_level(g_level); // set/store current contrast level
break;
case 2: // 2nd slider: fontsize (labelsize)
g_fs = (int)sliders[n + 3]->value();
break;
default:
break;
}
// update button label colors
update_labels();
w->window()->redraw();
}
// callback for the "random color" button
void rc_cb(Fl_Widget *w, void *v) {
static bool first = true;
unsigned int r, g, b;
if (first) {
// Seed the random number generator...
srand((unsigned int)time(NULL));
first = false;
r = g = b = 0; // initialize with black
} else {
r = rand() % 256;
g = rand() % 256;
b = rand() % 256;
}
sliders[1] -> value(r);
sliders[2] -> value(g);
sliders[3] -> value(b);
// update button label colors
lcolor = fl_rgb_color(r, g, b); // set color value
update_labels();
// output label color
char color_buf[10];
sprintf(color_buf, "%02X %02X %02X", r, g, b);
color_out->value(color_buf);
w->window()->redraw();
}
// callback for contrast algorithm (radio buttons)
void algo_cb(Fl_Widget *w, void *v) {
int val = fl_int(v);
g_algo = val;
switch(val) {
case FL_CONTRAST_LEGACY: alch = "LEGACY"; fl_contrast_mode(val); break; // legacy 1.3.x
case FL_CONTRAST_CIELAB: alch = "CIELAB"; fl_contrast_mode(val); break; // CIELAB L*a*b*
case FL_CONTRAST_CUSTOM: alch = "CUSTOM"; fl_contrast_mode(val); break; // custom
case FL_CONTRAST_NONE:
default:
alch = "none ";
fl_contrast_mode(FL_CONTRAST_NONE);
break;
}
g_level = fl_contrast_level(); // get current contrast level (per mode)
sliders[4]->value(g_level); // set level slider value
update_labels(); // update all button labels
// print selected button's attributes
if (g_selected >= 0) {
button_cb(buttons[g_selected], (void *)0);
}
if (w)
w->window()->redraw();
}
// color chooser callback
void color_cb(Fl_Widget *w, void *v) {
Fl_Color_Chooser *cc = (Fl_Color_Chooser *)w;
int r = (int)(cc->r() * 255);
int g = (int)(cc->g() * 255);
int b = (int)(cc->b() * 255);
Fl_Color c = fl_rgb_color(r, g, b);
Button *bt = buttons[255]; // last button
bt->color(c);
bt->set_labelcolor(lcolor);
bt->redraw();
}
// ===============================================================
// ====================== main() program ======================
// ===============================================================
int main(int argc, char **argv) {
const int bw = 58;
const int bh = 30;
int cw = 16 * bw + 10;
int ch = 16 * bh + 10;
int ww = cw + 10;
int wh = 16 * bh + 135 + 10 + 170 /* terminal */ + 10;
Fl_Double_Window window(ww, wh, "fl_contrast test");
int n = 0;
Button **b = buttons;
for (int y = 0; y < 16; y++) {
for (int x = 0; x < 16; x++) {
*b = new Button(x * bw + 10, y * bh + 10, bw, bh, n);
(*b)->set_labelcolor(n);
b++;
n++;
}
}
// sliders for label color (gray, red, green, blue)
const int sx = 10 + bw;
const int sw = 5 * bw;
const int sh = 25;
int sy = ch + 10;
Fl_Hor_Value_Slider *gray = new Fl_Hor_Value_Slider(sx, sy, sw, sh, "gray");
gray->color(0xdddddd00);
gray->textsize(13);
gray->align(FL_ALIGN_LEFT);
gray->value(0);
gray->bounds(0, 255);
gray->step(1);
gray->callback(color_slider_cb, (void *)0);
sy += sh + 10;
Fl_Hor_Value_Slider *red = new Fl_Hor_Value_Slider(sx, sy, sw, sh, "red");
red->color(FL_RED);
red->textcolor(FL_WHITE);
red->textsize(13);
red->align(FL_ALIGN_LEFT);
red->value(0);
red->bounds(0, 255);
red->step(1);
red->callback(color_slider_cb, (void *)1);
sy += sh + 5;
Fl_Hor_Value_Slider *green = new Fl_Hor_Value_Slider(sx, sy, sw, sh, "green");
green->color(FL_GREEN);
green->textsize(13);
green->align(FL_ALIGN_LEFT);
green->value(0);
green->bounds(0, 255);
green->step(1);
green->callback(color_slider_cb, (void *)1);
sy += sh + 5;
Fl_Hor_Value_Slider *blue = new Fl_Hor_Value_Slider(sx, sy, sw, sh, "blue");
blue->color(FL_BLUE);
blue->textcolor(FL_WHITE);
blue->textsize(13);
blue->align(FL_ALIGN_LEFT);
blue->value(0);
blue->bounds(0, 255);
blue->step(1);
blue->callback(color_slider_cb, (void *)1);
sliders[0] = gray;
sliders[1] = red;
sliders[2] = green;
sliders[3] = blue;
// contrast algorithm selection group
int cgx = 10 + 6*bw + 10;
int cgy = ch + 30;
int cgw = 90;
int cgh = 100;
int abh = 25;
Fl_Group *cg = new Fl_Group(cgx, cgy, cgw, cgh, "fl_contrast:");
cg->align(FL_ALIGN_TOP);
cg->box(FL_FRAME);
Fl_Radio_Round_Button *anon = new Fl_Radio_Round_Button(cgx, cgy, cgw, abh, "none");
Fl_Radio_Round_Button *aleg = new Fl_Radio_Round_Button(cgx, cgy + 25, cgw, abh, "LEGACY");
Fl_Radio_Round_Button *acie = new Fl_Radio_Round_Button(cgx, cgy + 50, cgw, abh, "CIELAB");
Fl_Radio_Round_Button *aapc = new Fl_Radio_Round_Button(cgx, cgy + 75, cgw, abh, "CUSTOM");
acie->value(1);
anon->callback(algo_cb, (void *)0);
aleg->callback(algo_cb, (void *)1);
acie->callback(algo_cb, (void *)2);
aapc->callback(algo_cb, (void *)3);
cg->end();
color_out = new Fl_Output(10 + 10 * bw, ch + 10, 100, 30, "label color:");
color_out->align(FL_ALIGN_LEFT);
color_out->textfont(FL_COURIER);
color_out->textsize(16);
color_out->value("00 00 00");
// light blue "level" slider
Fl_Hor_Value_Slider *s_level = new Fl_Hor_Value_Slider(10 + 9 * bw, red->y(), 3 * bw - 15, sh, "level");
s_level->color(231);
s_level->textcolor(224);
s_level->textsize(13);
s_level->align(FL_ALIGN_LEFT);
s_level->step(1);
s_level->bounds(0, 100);
s_level->value(g_level);
s_level->callback(slider_cb, (void *)1);
s_level->tooltip("set contrast sensitivity level (0-100), default: 50");
// labelsize slider
Fl_Hor_Value_Slider *s_fs = new Fl_Hor_Value_Slider(10 + 9 * bw, green->y(), 3 * bw - 15, sh, "labelsize");
s_fs->color(231);
s_fs->textcolor(224);
s_fs->textsize(13);
s_fs->align(FL_ALIGN_LEFT);
s_fs->step(1);
s_fs->bounds(8, 24);
s_fs->value(15);
s_fs->callback(slider_cb, (void *)2);
s_fs->tooltip("set label/text fontsize");
sliders[4] = s_level;
sliders[5] = s_fs;
// line feed (LF) button
Fl_Button *lf = new Fl_Button(10 + 8 * bw, blue->y(), bw, sh, "LF");
lf->tooltip("Click to output a linefeed to the log.");
lf->callback(lf_cb);
// random color (R) button
Fl_Button *rc = new Fl_Button(10 + 8 * bw + lf->w() + 2, blue->y(), bw*3/4, sh, "&RC");
rc->tooltip("Click to select a random text color.");
rc->callback(rc_cb);
// color chooser for field #255
int ccx = 10 + 12 * bw;
int ccy = ch + 10;
int ccw = 4 * bw;
int cch = 120;
Fl_Color_Chooser *color_chooser = new Fl_Color_Chooser(ccx, ccy, ccw, cch);
color_chooser->callback(color_cb);
color_chooser->label("bg color [255] @->");
color_chooser->rgb(1, 1, 1);
color_chooser->mode(1); // byte mode
color_chooser->align(FL_ALIGN_LEFT_BOTTOM);
// set contrast mode and level, update button label colors
fl_contrast_mode(g_algo);
fl_contrast_function(custom_contrast); // dummy contrast function
algo_cb(acie, fl_voidptr(2));
// Fl_Terminal for output
int ttx = 10;
int tty = color_chooser->y() + cch + 10;
int ttw = window.w() - 20;
int tth = window.h() - tty - 10;
term = new Fl_Terminal(ttx, tty, ttw, tth);
term->color(FL_WHITE);
term->textfgcolor(FL_BLACK);
term->textsize(13);
term->printf("FLTK %d.%d.%d fl_contrast() test program with different contrast algorithms, version %s\n",
FL_MAJOR_VERSION, FL_MINOR_VERSION, FL_PATCH_VERSION, version);
term->printf(" - Select a foreground (text) color with the gray or red/green/blue sliders (displayed inside each field).\n");
term->printf(" - Select an arbitrary background color for field #255 with the color chooser.\n");
term->printf(" - Select a colored field (by clicking on it) to display its attributes.\n");
term->printf(" - Select the contrast algorithm by clicking on the radio buttons.\n");
term->printf(" - Tune the contrast algorithm with the light blue \"level\" slider (default: %d).\n", fl_contrast_level());
term->printf(" - Select a random foreground (text) color by clicking the RC button\n");
window.resizable(term);
window.end();
window.show(argc, argv);
rc_cb(rc, 0); // update button labels - must be called after show()
return Fl::run();
}