c++ defined 16bit (high) color
Asked Answered
W

8

8

I am working on a project with a TFT touch screen. With this screen there is an included library. But after some reading, I still don't get something. In the library there are some defines regarding colors:

/* some RGB color definitions                                                 */
#define Black           0x0000      /*   0,   0,   0 */
#define Navy            0x000F      /*   0,   0, 128 */
#define DarkGreen       0x03E0      /*   0, 128,   0 */
#define DarkCyan        0x03EF      /*   0, 128, 128 */
#define Maroon          0x7800      /* 128,   0,   0 */
#define Purple          0x780F      /* 128,   0, 128 */
#define Olive           0x7BE0      /* 128, 128,   0 */
#define LightGrey       0xC618      /* 192, 192, 192 */
#define DarkGrey        0x7BEF      /* 128, 128, 128 */
#define Blue            0x001F      /*   0,   0, 255 */
#define Green           0x07E0      /*   0, 255,   0 */
#define Cyan            0x07FF      /*   0, 255, 255 */
#define Red             0xF800      /* 255,   0,   0 */
#define Magenta         0xF81F      /* 255,   0, 255 */
#define Yellow          0xFFE0      /* 255, 255,   0 */
#define White           0xFFFF      /* 255, 255, 255 */
#define Orange          0xFD20      /* 255, 165,   0 */
#define GreenYellow     0xAFE5      /* 173, 255,  47 */
#define Pink                        0xF81F

Those are 16-bit colors. But how do they go from: 0, 128, 128(dark cyan) to 0x03EF. I mean, how do you convert a 16-bit color to a uint16? This doesn't need to have an answer in code, because I just want to add some colors in the library. A link to a online converter (which I could not find) would be okay as well :)

Thanks

Wsan answered 5/12, 2012 at 10:21 Comment(4)
How many bits per component? Also, post some colors with red, and one that's white.Din
What do you mean, "how do you convert a 16bit color to a uint16?" Isn't the value 0x03EF a reasonable initializer for a uint16 variable?Donnydonnybrook
I mean the value for RGB to Uint 16, so if you have R=128, G=128, B=0 how do you convert that to uint 16 ;)Wsan
> A link to a online converter (wich i could not find) would be okay as well :) May it's newest than your question, but please let me leave it here if it's help for anyone else.. barth-dev.de/online/rgb565-color-picker/#Dincolo
B
6

From these one can easily find out the formula:

#define Red             0xF800      /* 255,   0,   0 */  
#define Magenta         0xF81F      /* 255,   0, 255 */
#define Yellow          0xFFE0      /* 255, 255,   0 */

F800 has 5 MSB bits set and FFE0 has 5 LSB not set. 0xF81F has obviously both 5 LSB's and 5 MSB's set, which proves the format to be RGB565.

The formula to convert a value 173 to Red is not as straightforward as it may look -- you can't simply drop the 3 least significant bits, but have to linearly interpolate to make 255 to correspond to 31 (or green 255 to correspond to 63).

NewValue = (31 * old_value) / 255;

(And this is still just a truncating division -- proper rounding could be needed)

With proper rounding and scaling:

Uint16_value = (((31*(red+4))/255)<<11) | 
               (((63*(green+2))/255)<<5) | 
               ((31*(blue+4))/255);

EDIT Added parenthesis to as helpfully suggested by JasonD.

Broughton answered 5/12, 2012 at 10:35 Comment(8)
Actually I think you're wrong. Truncating is exactly the right thing to do in order get a completely even distribution of the original 256 colours to the new range of 32 or 64. You really want to do value = (32 * old) / 256, which is exactly the same as a shift. Values 31 and 255 imply that your ranges are 31 and 255, but that is ignoring that they start at zero.Defunct
@JasonD: truncating decreases the average power of the signal on average by one half of the quantization level. Rounding avoids that. Also by induction to two quantization levels one should notice that it's not the number of bits, that is important, but the distance of the value represented by the smallest and largest quantization level. In case of 2 levels, the distance is 1. In case of 256 levels, the distance is 255 levels.Broughton
If you take your given "proper rounding and scaling" version and work out how each input value maps to an output, the distribution is pretty horrid. Some output values map to 9 input values, most to 8, and the largest value only maps to 1. A simple smooth gradient, quantized by your method, will be pretty nasty. Your rounding has virtually no effect, reproducing the uneven pattern, but slightly shifted. Another way to look at it, is that the value '0' represents the range of values 0 -> 0.999 recurring. So a range 0 - 255 actually means 0 -> 255.999 recurring, a delta of ~256, not 255.Defunct
I don't buy the explanation of recurrence, as we can easily enumerate all quantization levels. The formula suggested by me can produce horrific distribution and that's perhaps something worth of improving. But to me the more fundamental issue is that the overall intensity of the image doesn't change (as when simply truncating the bits). Perhaps a saturating arithmetic is then needed: R' = Saturate(R+4) >> 3;Broughton
Take an image which has evenly distributed brightness. If the range is 0 -> 255, the average will be 127.5. In your 5-bit quantized image, the average will be 15.019531 (rounded) or 15.003906 (non-rounded). In order to not darken the image, you would want to see an average of 15.5 (i.e. half of 31, the maximum brightness). Your saturated version is 15.984375, over-brightning instead.Defunct
The correct answer is to just truncate the values with a shift. The distribution will be completely even, and the average value will be exactly correct, preserving brightness. The part that goes wrong is converting in the other direction, which requires more than a simple shift. Which is what I said in the first place :)Defunct
let us continue this discussion in chatBroughton
@AkiSuihkonen & JasonD Thanks you guys, i've got the colors i want now thanks to you to discussing it here :)Wsan
M
6

You need to know the exact format of the display, just "16-bit" is not enough.

There's RGB555, in which each of the three components get 5 bits. This drops the total color space to just 32,768 colors, wasting one bit but it's very simple to manage since the there's the same number of shades for each component.

There's also RGB565, in which the green component is given 6 bits (since the human eye is more sensitive to green). This might be the format you're having, since the "dark green" example is 0x03e0 which in binary is 0b0000 0011 1110 0000. Since there's 6 bits set to 1 there, I guess that's the total allocation for the green component and showing it's maximum value.

It's like this, then (with spaces separating every four bits and re-using the imaginary 0b prefix):

0bRRRR RGGG GGGB BBBB

Of course, the bit ordering can differ too, in the word.

The task of converting a triplet of numbers into a bit-packed word is quite easily done in typically programming languages that have bit manipulation operators.

In C, it's often done in a macro, but we can just as well have a function:

#include <stdint.h>

uint16_t rgb565_from_triplet(uint8_t red, uint8_t green, uint8_t blue)
{
  red   >>= 3;
  green >>= 2;
  blue  >>= 3;
  return (red << 11) | (green << 5) | blue;
}

note that the above assumes full 8-bit precision for the components, so maximum intensity for a component is 255, not 128 as in your example. If the color space really is using 7-bit components then some additional scaling would be necessary.

Metronymic answered 5/12, 2012 at 10:27 Comment(0)
B
6

From these one can easily find out the formula:

#define Red             0xF800      /* 255,   0,   0 */  
#define Magenta         0xF81F      /* 255,   0, 255 */
#define Yellow          0xFFE0      /* 255, 255,   0 */

F800 has 5 MSB bits set and FFE0 has 5 LSB not set. 0xF81F has obviously both 5 LSB's and 5 MSB's set, which proves the format to be RGB565.

The formula to convert a value 173 to Red is not as straightforward as it may look -- you can't simply drop the 3 least significant bits, but have to linearly interpolate to make 255 to correspond to 31 (or green 255 to correspond to 63).

NewValue = (31 * old_value) / 255;

(And this is still just a truncating division -- proper rounding could be needed)

With proper rounding and scaling:

Uint16_value = (((31*(red+4))/255)<<11) | 
               (((63*(green+2))/255)<<5) | 
               ((31*(blue+4))/255);

EDIT Added parenthesis to as helpfully suggested by JasonD.

Broughton answered 5/12, 2012 at 10:35 Comment(8)
Actually I think you're wrong. Truncating is exactly the right thing to do in order get a completely even distribution of the original 256 colours to the new range of 32 or 64. You really want to do value = (32 * old) / 256, which is exactly the same as a shift. Values 31 and 255 imply that your ranges are 31 and 255, but that is ignoring that they start at zero.Defunct
@JasonD: truncating decreases the average power of the signal on average by one half of the quantization level. Rounding avoids that. Also by induction to two quantization levels one should notice that it's not the number of bits, that is important, but the distance of the value represented by the smallest and largest quantization level. In case of 2 levels, the distance is 1. In case of 256 levels, the distance is 255 levels.Broughton
If you take your given "proper rounding and scaling" version and work out how each input value maps to an output, the distribution is pretty horrid. Some output values map to 9 input values, most to 8, and the largest value only maps to 1. A simple smooth gradient, quantized by your method, will be pretty nasty. Your rounding has virtually no effect, reproducing the uneven pattern, but slightly shifted. Another way to look at it, is that the value '0' represents the range of values 0 -> 0.999 recurring. So a range 0 - 255 actually means 0 -> 255.999 recurring, a delta of ~256, not 255.Defunct
I don't buy the explanation of recurrence, as we can easily enumerate all quantization levels. The formula suggested by me can produce horrific distribution and that's perhaps something worth of improving. But to me the more fundamental issue is that the overall intensity of the image doesn't change (as when simply truncating the bits). Perhaps a saturating arithmetic is then needed: R' = Saturate(R+4) >> 3;Broughton
Take an image which has evenly distributed brightness. If the range is 0 -> 255, the average will be 127.5. In your 5-bit quantized image, the average will be 15.019531 (rounded) or 15.003906 (non-rounded). In order to not darken the image, you would want to see an average of 15.5 (i.e. half of 31, the maximum brightness). Your saturated version is 15.984375, over-brightning instead.Defunct
The correct answer is to just truncate the values with a shift. The distribution will be completely even, and the average value will be exactly correct, preserving brightness. The part that goes wrong is converting in the other direction, which requires more than a simple shift. Which is what I said in the first place :)Defunct
let us continue this discussion in chatBroughton
@AkiSuihkonen & JasonD Thanks you guys, i've got the colors i want now thanks to you to discussing it here :)Wsan
H
4

It looks like you're using RGB565, first 5 bits for Red, then 6 bits for Green, then 5 bits for Blue.

You should mask with 0xF800 and shift right 11 bits to get the red component (or shift 8 bits to get a value from 0-255). Mask with 0x07E0 and shift right 5 bits to get green component (or 3 to get a 0-255 value). Mask with 0x001F to get the blue component (and shift left 3 bits to get 0-255).

Heber answered 5/12, 2012 at 10:35 Comment(0)
D
3

Your colours are in 565 format. It would be more obvious if you wrote them out in binary.

  • Blue, (0,0,255) is 0x001f, which is 00000 000000 11111
  • Green, (0, 255, 0) is 0x07e0, which is 00000 111111 00000
  • Red, (255, 0, 0) is 0xf800, which is 11111 000000 00000

To convert a 24 bit colour to 16 bit in this format, simply mask off the upper bits needed from each component, shift into position, and OR together.

The convert back into 24 bit colour from 16 bit, mask each component, shift into position, and then duplicate the upper bits into the lower bits.

In your examples it seems that some colours have been scaled and rounded rather than shifted.

I strongly recommend using the bit-shift method rather than scaling by a factor like 31/255, because the bit-shifting is not only likely to be faster, but should give better results.

Defunct answered 5/12, 2012 at 10:36 Comment(0)
D
1

The 3-part numbers you’re showing are applicable to 24-bit color. 128 in hex is 0x7f, but in your color definitions, it's being represented as 0x0f. Likewise, 255 is 0xff, but in your color definitions, it's being represented as 0x1f. This suggests that you need to take the 3-part numbers and shift them down by 3 bits (losing 3 bits of color data for each color). Then combine them into a single 16-bit number:

uint16 color = ((red>>3)<<11) | ((green>>2)<<5) | (blue>>3);

...revised from earlier because green uses 6 bits, not 5.

Donnydonnybrook answered 5/12, 2012 at 10:39 Comment(0)
T
1

You need to know how many bits there are per colour channel. So yes, there are 16 bits for a colour, but the RGB components are each some subset of those bits. In your case, red is 5 bits, green is 6, and blue is 5. The format in binary would look like so:

RRRRRGGG GGGBBBBB

There are other 16 bit colour formats, such as red, green, and blue each being 5 bits and then use the remaining bit for an alpha value.

The range of values for both the red and blue channels will be from 0 to 2^5-1 = 31, while the range for green will be 0 to 2^6-1 = 63. So to convert from colours in the form of (0->255),(0->255),(0->255) you will need to map values from one to the other. For example, a red value of 128 in the range 0->255 will be mapped to (128/255) * 31 = 15.6 in the red channel with range 0-31. If we round down, we get 15 which is represented as 01111 in five bits. Similarly, for the green channel (with six bits) you will get 011111. SO the colour (128,128,128) will map to 01111011 11101111 which is 0x7BEF in hexadecimal.

You can apply this to the other values too: 0,128,128 becomes 00000011 11101111 which is 0x03EF.

Toadeater answered 5/12, 2012 at 10:40 Comment(0)
M
1

Those colours shown in your code are RGB565. As shown by

#define Blue            0x001F      /*   0,   0, 255 */ 
#define Green           0x07E0      /*   0, 255,   0 */ 
#define Red             0xF800      /* 255,   0,   0 */

If you simply want to add some new colours to this #defined list, the simplest way to convert from 16bit UINT per channel is just to shift your values down to loose the the low order bits and then shift and (or) them into position in the 16bitRGB value. This could well produce banding artefacts though, and there may well be a better conversion method.

i.e.

UINT16 blue = 0xFF;
UINT16 green = 0xFF;
UINT16 red = 0xFF;


blue  >>= 11; // top 5 bits
green >>= 10; // top 6 bits
red   >>= 11; // top 5 bits

UINT16 RGBvalue = blue | (green <<5) | (red << 11)

You may need to mask of any unwanted stray bits after the shifts, as I am unsure how this works, but I think the code above should work.

Magyar answered 5/12, 2012 at 10:51 Comment(0)
K
1

Building on unwind's answer, specifically for the Adafruit GFX library using the Arduino 2.8" TFT Touchscreen(v2), you can add this function to your Arduino sketch and use it inline to calculate colors from rgb:

uint16_t getColor(uint8_t red, uint8_t green, uint8_t blue)
{
  red   >>= 3;
  green >>= 2;
  blue  >>= 3;
  return (red << 11) | (green << 5) | blue;
}

Now you can use it inline as so, illustrated with a function that creates a 20x20 square at x0,y0:

void setup() {
  tft.begin();
  makeSquare(getColor(20,157,217));
}

unsigned long makeSquare(uint16_t color1) {
  tft.fillRect(0, 0, 20, 20, color1);
}

Docs for the Adafruit GFX library can be found here

Korrie answered 18/5, 2014 at 17:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.