我正在为C中的32位皮质M0微控制器编写一些软件,而且我正在使用32位RGB值进行大量操作。它们以像0x00BBRRGG
这样的32位整数格式处理。我希望能够对它们进行数学计算,而不必担心颜色之间的携带位溢出,所以我需要将它们分成三个uint8值。有没有一种有效的方法呢?我假设效率低下的方式如下:
blue = (RGB >> 16) & 0xFF;
green = (RGB >> 8) & 0xFF;
red = RGB & 0xFF;
//do math
new_RGB = (blue << 16) | (green << 8) | red;
此外,我有几个接口,其中一个使用格式0x00RRGGBB
,另一个使用0x00BBRRGG
。有没有一种有效的方法在两者之间进行转换?
答案 0 :(得分:2)
如果您使用struct
,则不需要进行任何位移操作。我不知道这对你的特定处理器是否有效,但只是做一些简单的事情:
typedef struct xRGBPixel {
unsigned char unused;
unsigned char red;
unsigned char green;
unsigned char blue;
} xRGBPixel;
您可以为BRG像素设置类似的结构。 (你确定它是BRG而不是BGR吗?那是非常奇怪和非传统的。)
如果效率不高,那么Jonathan Leffler在关于32位int
和4个unsigned char
数组的联合的评论中的建议可能是更合适。像这样:
typedef union Pixel {
uint32_t pixelAsInt;
unsigned char pixelAsChar[4];
} Pixel;
答案 1 :(得分:1)
要将0x00RRGGBB转换为0x00BBRRGG,您可以使用endian转换器:
REV r0,r0 ;0x00RRGGBB -> 0xBBGGRR00
LSRS r0,r0,#8 ;0xBBGGRR00 -> 0x00BBGGRR
执行此操作的有效方法可以是编写一个汇编函数,将最大量的数据加载到空闲寄存器中,对所有寄存器执行转换,然后将其写回。
使用ARM procedure call standard作为参考,介绍如何编写从C调用的汇编函数。
另一种方法是简单地执行字节副本,但这需要3-4 *读/写,其中上面只需要每像素2个。
* 3如果不关心xxRRGGBB,4如果00RRGGBB。
答案 2 :(得分:1)
我希望能够对它们进行数学计算,而不必担心颜色之间的进位溢出,所以我需要将它们分成三个uint8值。
不,通常你不需要(将它们分成三个uint8值)。考虑这个功能:
uint32_t blend(const uint32_t argb0, const uint32_t argb1, const int phase)
{
if (phase <= 0)
return argb0;
else
if (phase < 256) {
const uint32_t rb0 = argb0 & 0x00FF00FF;
const uint32_t rb1 = argb1 & 0x00FF00FF;
const uint32_t ag0 = (argb0 >> 8) & 0x00FF00FF;
const uint32_t ag1 = (argb1 >> 8) & 0x00FF00FF;
const uint32_t rb = rb1 * phase + (256 - phase) * rb0;
const uint32_t ag = ag1 * phase + (256 - phase) * ag0;
return ((rb & 0xFF00FF00u) >> 8)
| (ag & 0xFF00FF00u);
} else
return argb1;
}
此函数通过拆分每个输入向量(具有四个8位组件)实现从颜色argb0
(phase <= 0
)到argb1
(phase >= 256
)的线性混合分成两个16位分量的向量。
如果你不需要alpha通道,那么处理成对的颜色值(比如每对像素)可能会更有效 - 所以(0xRRGGBB
,0xrrggbb
)被分为(0x00RR00BB
,0x00rr00bb
,0x00GG00gg
) - 在上面的blend
函数中意味着一个较少的乘法(但是一个AND和一个OR运算)。
Cortex-M0器件上的32位乘法运算因实现而异。一些具有单周期乘法运算,而另一些则需要32个周期。因此,根据所使用的精确Cortex-M0内核,用AND和OR替换一个乘法可能是一个很大的加速,或者是一个轻微的减速。
当你确实需要单独的组件时,将拆分留给编译器通常会产生更好的代码:而不是指定颜色,将指针传递给颜色值,
uint32_t some_op(const uint32_t *const argb)
{
const uint32_t a = ((const uint8_t *)argb)[0];
const uint32_t r = ((const uint8_t *)argb)[1];
const uint32_t g = ((const uint8_t *)argb)[2];
const uint32_t b = ((const uint8_t *)argb)[3];
/* Do something ... */
}
这是因为许多架构都有指令将8位值加载到完整寄存器中,将所有高位设置为零(Cortex-M0架构上的零扩展,uxtb
; C编译器会为你做这个)。标记指针和指向的值以及中间值const
应该允许编译器优化访问,以便它在生成的代码中的最佳时刻/位置发生,而不是必须将它保存在寄存器中。 (在具有少量(可用)寄存器的架构上尤其如此,例如32位和64位Intel和AMD架构(x86和x86-64).Cortex-M0有12个通用32位寄存器,但它取决于在ABI上使用哪些“免费”在函数中使用。)
请注意,如果您使用GCC编译代码,则可以使用
uint32_t oabc_to_ocba(uint32_t c)
{
asm volatile ( "rev %0, %0\n\t"
: "=r" (c)
: "r" (c)
);
return c >> 8;
}
将0x0ABC
转换为0x0CBA
,反之亦然。通常,它会编译为rev r0, r0
,lsrs r0, r0, #8
,bx lr
,但编译器可以内联它并使用另一个寄存器(r0
)。
答案 3 :(得分:0)
它不是便携式的,但是因为你处于M0并且可能是小端模式。使用位字段或uint32_t与uint8_t数组的并集。
typedef struct {
uint32_t red: 8;
uint32_t green: 8;
uint32_t blue: 8;
uint32_t spare: 8;
} rgb_s;
static rgb_s var; // statics init to zero
var.red = 0x56
var.green = 0x34
var.blue = 0x12
uint32_t myInt = *(uint32_t*)&var; // myInt is now 0x00123456;
使用静态或确保备用字段在重要时归零。
或工会
enum {Red, Green, Blue, Colors};
typedef union {
uint32_t rgb;
uint8_t color[Colors];
} rgb_u;
rgb_u var;
var.rgb = 0x0;
var.color[red] = 0x56;
var.color[green] = 0x34;
var.color[blue] = 0x12;
assert(var.rgb == 0x123456); //the uint32 overlays the array
同样,两者都不是真正可移植的,但两者在嵌入式中都很常见。您需要知道处理器的endian。 (M0可以大或小但是默认值很小) 现在还有匿名工会是C,但并非所有嵌入式编译器都支持它们。
答案 4 :(得分:0)
你的“低效”方式可能归结为几行机器代码和转换速度很快 - 这意味着转换版本将以极快的速度执行,并且99%的应用程序中的微观优化不应成为关注点
通过指针/数组寻址单个字节不一定是性能改进。它可能恰恰相反 - 检查生成的程序集。如果你要使用struct / union解决方案,那应该是为了便于阅读,而不是为了微观管理性能。
然而,移动版本在便携性方面更胜一筹。当位移时,您不必担心字节序,填充,对齐,指针别名 - 所有这些都可能是struct / union解决方案的问题。
问题的根源实际上是32位整数表示。如果你能摆脱它,它将解决很多问题。这里理想的格式是uint8_t color[3];
。