C++中定义的16位(高位)颜色。

8

我正在开发一个与TFT触摸屏相关的项目。这个屏幕自带一个库。然而,经过一些阅读,我仍然不明白其中的某些内容。在库中,有一些有关颜色的定义:

/* 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

那些是16位颜色。但是它们如何从0, 128, 128(深青色)转换为0x03EF呢?我的意思是,你如何将16位颜色转换为uint16?这不需要在代码中回答,因为我只想在库中添加一些颜色。在线转换器的链接(我找不到)也可以 :)
谢谢。

每个组件有多少位?此外,发布一些带红色的颜色和一个白色的颜色。 - Pubby
你是什么意思,"如何将16位彩色转换为uint16?" 0x03EF的值不是uint16变量的合理初始值吗? - phonetagger
我的意思是将RGB值转换为Uint 16,如果你有R=128,G=128,B=0,你该如何将其转换为uint 16 ;) - Bart Teunissen
1
一个在线转换器的链接(我找不到)也可以。也许它比你的问题更新,但如果它对其他人有帮助,请允许我在这里留下它的链接。 http://www.barth-dev.de/online/rgb565-color-picker/# - Gyula Madarasz
8个回答

6
你需要知道显示器的确切格式,仅仅"16位"是不够的。
有RGB555格式,其中每个分量都占5位。这将总颜色空间减少到32,768种颜色,浪费了一个位,但很容易管理,因为每个分量有相同数量的阴影。
还有RGB565格式,绿色分量被赋予6位(因为人眼对绿色更敏感)。这可能是您正在使用的格式,因为"深绿色"示例是0x03e0,在二进制中为0b0000 0011 1110 0000。因为有6位设置为1,所以我猜这是绿色分量的总分配,并显示它的最大值。
然后是这样的(用空格隔开每四位并重新使用想象中的0b前缀):
0bRRRR RGGG GGGB BBBB

当然,在一个字中,位的顺序也可能不同。

将三个数字转换为位压缩字的任务在通常具有位操作符的编程语言中很容易完成。

在C中,通常使用宏来完成这个任务,但我们同样可以使用函数:

#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;
}

请注意,上述内容假设组件具有完整的8位精度,因此组件的最大强度为255,而不是您示例中的128。如果颜色空间确实使用7位组件,则需要进行一些额外的缩放。

6

从这些内容中,我们可以轻松地找到公式:

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

F800 的前 5 位为 1,FFE0 的后 5 位为 0。 0xF81F 很明显同时前 5 位和后 5 位都为 1,这证明它是 RGB565 格式。

将值 173 转换为红色的公式并不是那么简单 -- 你不能简单地抛弃掉最低的 3 个有效位,而必须进行线性插值,使得 255 对应于 31(或者绿色的 255 对应于 63)。

NewValue = (31 * old_value) / 255;

(这仍然只是一个截断除法 - 可能需要正确的四舍五入)
(通过正确的四舍五入和缩放)
Uint16_value = (((31*(red+4))/255)<<11) | 
               (((63*(green+2))/255)<<5) | 
               ((31*(blue+4))/255);

编辑 根据JasonD的建议添加了括号。


其实我认为你错了。在将原始256种颜色完全均匀地分配到新的32或64个范围时,截断是完全正确的方式。你确实想要做的是value = (32 * old) / 256,这与移位完全相同。值31和255意味着您的范围为31和255,但这忽略了它们从零开始的事实。 - JasonD
@JasonD:截断会使信号的平均功率平均降低了量化级别的一半。四舍五入可以避免这种情况发生。同时,通过归纳,对于两个量化级别,应该注意到重要的不是位数,而是由最小和最大量化级别所表示的值之间的距离。在2个级别的情况下,距离为1。在256个级别的情况下,距离为255个级别。 - Aki Suihkonen
如果你使用你所谓的“正确舍入和缩放”版本并计算每个输入值对应的输出值,分布会相当糟糕。一些输出值对应于9个输入值,大多数对应于8个,而最大值仅对应1个。一个简单的平滑渐变,通过您的方法进行量化,将会非常不好看。你的舍入几乎没有影响,只是稍微偏移了一下不均匀的模式。另一种看待它的方式是,值“0”代表值范围为0->0.999重复。因此,范围0-255实际上意味着0->255.999重复,增量约为256,而不是255。 - JasonD
获取一张亮度均匀分布的图像。如果范围是0->255,则平均值应为127.5。在经过5位量化处理的图像中,平均值将约为15.019531(四舍五入)或15.003906(非四舍五入)。为了不使图像变暗,您希望看到一个平均值为15.5的图像(即31的一半,最大亮度)。而您过饱和的版本则达到15.984375,反而使其过于明亮。 - JasonD
@AkiSuihkonen和JasonD,谢谢你们,我现在得到了我想要的颜色,感谢你们在这里讨论它 :) - Bart Teunissen
显示剩余3条评论

4
看起来你正在使用RGB565,前5位用于红色,接下来6位用于绿色,最后5位用于蓝色。
为了获取红色分量(或者从0到255的值),你应该使用0xF800进行掩码处理并右移11位(或者右移8位)。 为了获取绿色分量(或者从0到255的值),你应该使用0x07E0进行掩码处理并右移5位(或者右移3位)。 为了获取蓝色分量(并左移3位以获取0-255),你应该使用0x001F进行掩码处理。

3
你的颜色使用的是565格式。如果你以二进制方式书写它们,将更加明显。
- 蓝色(0,0,255)是0x001f,即00000 000000 11111 - 绿色(0,255,0)是0x07e0,即00000 111111 00000 - 红色(255,0,0)是0xf800,即11111 000000 00000
要将24位颜色转换为此格式中的16位颜色,只需从每个分量中掩盖所需的上位比特位,移位到相应的位置,然后使用OR运算合并这些位。
要从16位重新转换回24位颜色,需要掩盖每个分量,移位到相应的位置,然后将上位比特位翻倍为下位比特位。
在你的示例中,似乎对某些颜色进行了缩放和四舍五入,而不是移位。
我强烈建议使用移位方法,而不是通过31/255等因子来缩放,因为移位不仅可能更快,而且效果应该更好。

1
您所显示的三部分数字适用于24位色彩。十六进制中的128是0x7f,但在您的颜色定义中,它被表示为0x0f。同样,255是0xff,但在您的颜色定义中,它被表示为0x1f。这表明您需要将三个数字向下移动3位(每种颜色失去3位颜色数据)。然后将它们合并成一个16位数字。
uint16 color = ((red>>3)<<11) | ((green>>2)<<5) | (blue>>3);

...这个版本进行了修订,因为绿色使用的是6位而不是5位。


1

你代码中显示的颜色是RGB565格式。如下所示:

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

如果您只是想将一些新颜色添加到此#defined列表中,最简单的方法是将16位每通道的UINT值向下移位以丢弃低阶位,然后将它们移位并(或)放置在16位RGB值中。但这可能会产生带状伪影,因此可能存在更好的转换方法。
例如:
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)

在进行移位操作后,您可能需要屏蔽任何不需要的杂散位,因为我不确定这个操作的具体情况,但我认为上面的代码应该可以正常工作。


1
建立在unwind的答案基础上,特别针对使用Arduino 2.8英寸TFT触摸屏(v2)的Adafruit GFX库,您可以将此函数添加到Arduino草图中,并在内联中使用它来计算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;
}

现在你可以内联使用它,如下所示,使用一个创建20x20正方形在x0,y0处的函数进行说明:
void setup() {
  tft.begin();
  makeSquare(getColor(20,157,217));
}

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

可以在这里找到有关Adafruit GFX库的文档。


1

您需要知道每个颜色通道有多少位。因此,每种颜色都有16位,但RGB组件是这些位的某些子集。在您的情况下,红色为5位,绿色为6位,蓝色为5位。二进制格式如下:

RRRRRGGG GGGBBBBB

还有其他16位色彩格式,例如红色、绿色和蓝色每个颜色占用5位,然后使用剩余的一位作为alpha值。

红色和蓝色通道的值范围将从02^5-1 = 31,而绿色的范围将是02^6-1 = 63。因此,要将形式为(0->255),(0->255),(0->255)的颜色转换,您需要将一个值映射到另一个值。例如,在0->255范围内的红色值为128将被映射到具有范围0-31的红色通道中的(128/255) * 31 = 15.6。如果我们向下取整,我们得到15,它表示为五位二进制数01111。类似地,对于带有六位的绿色通道,您将得到011111。因此,颜色(128,128,128)将映射到01111011 11101111,即十六进制0x7BEF
您也可以将此应用于其他值:0,128,128 变成了 00000011 11101111,即 0x03EF

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接