I am using uGui to display fonts on screen. But the final result is not as satisfactory as it should be. The hardware is quite limited: 8 bpp color (256 colors total), with 3 bits for red, 3 bits for green, and 2 bits for blue ( 3:3:2 ).
The original code to get the color of a point is:
color = (((fc & 0xFF) * b + (bc & 0xFF) * (256 - b)) >> 8) & 0xFF |//Blue component
(((fc & 0xFF00) * b + (bc & 0xFF00) * (256 - b)) >> 8) & 0xFF00|//Green component
(((fc & 0xFF0000) * b + (bc & 0xFF0000) * (256 - b)) >> 8) & 0xFF0000; //Red component
Being:
fc
:uint32_t
, the ink color for that character.bc
:uint32_t
, the background color for that character.b
:uint8_t
, ink/background mixture percentage for a specific point of the character.
This last data is taken from the font file, previously rendered (using an external utility) and converted to array[]
de unsigned char
, in which each element represents the ink/background ratio of the dot.
The results of the original code are quite satisfactory:
But there are several color artifacts (especially in the A
). If we zoom ...
In an attempt on my part to limit or eliminate this, and taking into account that only certain colors are used for the text/background, it occurred to me to artificially limit the number of possible colors to be applied for smoothing : for example, if the ink is black, use only the gray color for the points that are not ink or background.
The code was like this:
// Calculamos el color para el antialiasing.
UG_COLOR acolor; // unsigned char
switch( fc ) {
case 0x92: // GRAY
acolor = 0xDB;
break;
case 0x1C: // GREEN
acolor = 0x5E;
break;
case 0xF0: // ORANGE
acolor = 0x90;
break;
case 0xDB: // SEMI_WHITE
acolor = 0xFF;
break;
default: // BLACK
acolor = 0xB6; // GRAY;
break;
}
color = (((fc & 0xFF) * b + (bc & 0xFF) * (256 - b)) >> 8) & 0xFF |//Blue component
(((fc & 0xFF00) * b + (bc & 0xFF00) * (256 - b)) >> 8) & 0xFF00|//Green component
(((fc & 0xFF0000) * b + (bc & 0xFF0000) * (256 - b)) >> 8) & 0xFF0000; //Red component
// Si no vamos a pintar ni con la tinta ni con el fondo, lo hacemos
// con el color para el antialiasing.
if( ( color != fc ) && ( color != bc ) ) color = acolor;
The result was... different :
It can be seen that the inappropriate colored dots have almost disappeared, but the overall quality ... leaves a lot to be desired.
What is the correct way to perform this type of smoothing? What bit combination/operation should I perform?
Note: C or C++, bitwise operations are the same in both.
EDIT
In reply to @abufalia.
To paint a pixel on the screen, use a unsigned char
:
*((unsigned char *)(posicion-en-memoria)) = color;
Being color
a UG_COLOR, which in turn is a uint32_t
.
In all paint text operations, the value of fc
y bc
is limited to 8 bits:
const UG_COLOR BLACK = 0X00;
const UG_COLOR GRAY = 0x92;
const UG_COLOR SEMI_WHITE = 0xDB;
const UG_COLOR WHITE = 0XFF;
const UG_COLOR GREEN = 0X1C;
const UG_COLOR ORANGE = 0XF0;
To obtain the percentage of mixture, use:
b = font->p[index++];
being:
typedef struct {
unsigned char* p;
FONT_TYPE font_type;
UG_S16 char_width;
UG_S16 char_height;
UG_U16 start_char;
UG_U16 end_char;
UG_U8 *widths;
} UG_FONT;
I don't know... but that function has certain shortcomings or I haven't understood something from the question:
The output of the program is:
I don't know, but unless I've misunderstood something, I'm not convinced by those results:
switch
( itfc
is defined in 1 byte)7c
=011 111 00
). Here I would rather expect a value similar to6e
(011 011 10
) or6d
.That function doesn't seem to go very well since it invents colors... which coincidentally is what happens to you.
I would start by replacing that algorithm with a slightly more predictable one:
ADDED BY THE AUTHOR OF THE QUESTION
Since there is no way to test it without the proper hardware, I'm posting the results of this excellent answer here:
Although they are similar to the originally generated ones, they are darker tones in general, which makes those apparently incorrect points barely noticeable; in fact, on the actual display (remember that, to display on a PC, you have to map from 8bpp to 24bpp; that makes colors inaccurate), the results are nothing short of perfect.
I can't be 100% sure but I think the formula you use is correct, what is not correct is the color treatment.
I understand that if you are mixing black font color with white background, the result should be grays; The gray color has the particularity that all its RGB components have the same value, for example 50% black and white , 75% white 25% black , 25% white 75% black ...
Your color structure is r3g3b2 but the treatment you do is r8g8b8, so if I pass these values:
fc
:0x00000000
(all bits at 0: black).bc
:0xffffffff
(all bits at 1: blank).b
:0x7f
(half of the maximum ofuint8_t
: 50%).With your formula I get the value 128, which if we interpret it as r3g3b2 is 57.14% red 0% green and 0% blue , that is: red.
What you need is to treat the components as r3g3b2, for this I suggest you create some functions that allow you to obtain each component (C++ code):
I don't know exactly how the alpha blend works, but having these functions the operation could look like this:
The algorithm may not work with colors other than gray (mixing red and blue should come out fuzzy).
I think the sizes of the data types in which you store colors are not clear.
On the one hand, you say that
fc
andbc
are of typeuint32_t
, which leads us to think that they use 8 bits for each component, that is, it is 24-bit RGB (and therefore white would be stored as0xFFFFFF
).In fact, the function that computes the color mix between
fc
andbc
that you've provided is written assuming that both colors are RGB with 8 bits per component (look at the masks you apply, like0xFF
,0xFF00
and0xFF0000
).However, in the
switch
one you put later, it seems to be deduced that each color is actually 8 bits, since for example it0xF0
is labeled "orange".If this were the case, then @eferion's answer would be the one that correctly computes the color mix, with masks using the (3:3:2) split of bits per component.
It could also be the case that the ink and background colors actually come to you in 24 bits, so your formula would be correct. In that case the color 0xFF does not represent white (as @eferion supposed), but a very bright green (
0x0000FF
), and the result of the formula for decreasing values ofb
would be fine, giving darker and darker greens (0x0000FE
,0x00007C
,0x000000
).But since the formula produces a 24-bit RGB color and your display handles 8-bit colors, there would still be an extra step where those 24 bits are converted to 8 (3:3:2) and it might be that step who does it wrong. If this were the case, we would not be looking at that step. Maybe the hardware does it.
You must clarify in what format the ink and paper colors come to you (whether in 8 or 24 bits), and in what format you must generate the resulting color (whether in 8 or 24 bits).