如何最有效地修改R / G / B值?

时间:2015-02-08 13:46:22

标签: c++ optimization rgb pixel

所以我想在我的基于像素的渲染系统中实现光照,谷歌搜索并发现显示R / G / B值更亮或更暗我必须将每个红绿色和蓝色值乘以数字<如图1所示,将其显示得更暗并且通过数字> 1显示它更轻。

所以我像这样实现了它,但它真的拖累了我的性能,因为我必须为每个像素执行此操作:

void PixelRenderer::applyLight(Uint32& color){
    Uint32 alpha = color >> 24;
    alpha << 24;
    alpha >> 24;

    Uint32 red = color >> 16;
    red = red << 24;
    red = red >> 24;

    Uint32 green = color >> 8;
    green = green << 24;
    green = green >> 24;

    Uint32 blue = color;
    blue = blue << 24;
    blue = blue >> 24;

    red = red * 0.5;
    green = green * 0.5;
    blue = blue * 0.5;
    color = alpha << 24 | red << 16 | green << 8 | blue;
}

关于如何提高速度的任何想法或例子?

5 个答案:

答案 0 :(得分:3)

试试这个:(编辑:事实证明,这只是一个可读性改进,但请继续阅读以获得更多见解。)

void PixelRenderer::applyLight(Uint32& color)
{
    Uint32 alpha = color >> 24;
    Uint32 red = (color >> 16) & 0xff;
    Uint32 green = (color >> 8) & 0xff;
    Uint32 blue = color & 0xff;
    red = red * 0.5;
    green = green * 0.5;
    blue = blue * 0.5;
    color = alpha << 24 | red << 16 | green << 8 | blue;
}

话说回来,你应该明白,使用通用处理器(如计算机的CPU)执行那种操作必然会非常慢。这就是发明硬件加速显卡的原因。

修改

如果你坚持以这种方式操作,那么你可能不得不诉诸黑客以提高效率。在处理8位通道值时经常使用的一种类型的hack是查找表。使用查找表,不是将每个单独的通道值乘以浮点数,而是预先计算256个值的数组,其中数组的索引是通道值,并且该索引中的值是将通道值乘以的预计算结果漂浮。然后,在转换图像时,只需使用通道值查找数组的条目,而不是执行实际的浮点乘法。这要快得多。 (但仍然没有编程专用的那么快,大规模并行硬件为你做那些事情。)

修改

正如其他人已经指出的那样,如果你不打算在alpha通道上运行,那么你不需要提取它然后再应用它,你可以保持不变。所以,你可以color = (color & 0xff000000) | red << 16 | green << 8 | blue;

答案 1 :(得分:3)

这样的移位和遮罩在现代处理器上通常非常快。我可能会看一些其他的事情:

  1. 遵循优化的第一条规则 - 分析您的代码。您可以通过调用该方法数百万次并对其进行计时来完成此操作。你的计算速度慢,还是别的?什么慢?尝试省略部分方法 - 加快速度吗?
  2. 确保此函数是内联声明的(并确保它实际上已内联)。函数调用开销将大大超过像素操作(特别是如果它是虚拟的)。
  3. 考虑声明方法Uint32 PixelRenderer::applyLight(Uint32 color)并返回修改后的值,这可能有助于避免一些解除引用并为编译器提供一些额外的优化机会。
  4. 避免fp进行整数转换,它们可能非常昂贵。如果普通整数除法不足,请查看使用定点数学。
  5. 最后,查看汇编程序以查看编译器生成的内容(具有优化功能)。有分支或转换吗?你的方法实际上已经内联了吗?

答案 2 :(得分:2)

要保留前端使用中的alpha值:

(color>>1)&0x7F7F7F | (color&0xFF000000)

(关于Wimmel在评论中提供的内容的调整)。

我认为这里的'学习曲线'是你使用shift并转回来掩盖比特。您应该将&与屏蔽值一起使用。

对于更通用的解决方案(0.0<=factor<=1.0):

void PixelRenderer::applyLight(Uint32& color, double factor){
    Uint32 alpha=color&0xFF000000;
    Uint32 red=  (color&0x00FF0000)*factor;
    Uint32 green= (color&0x0000FF00)*factor;
    Uint32 blue=(color&0x000000FF)*factor;

   color=alpha|(red&0x00FF0000)|(green&0x0000FF00)|(blue&0x000000FF);
}

请注意,在执行乘法运算之前,无需将组件向下移位到低位。

最终,您可能会发现瓶颈是浮点转换和算术。

要减少这一点,您应该考虑:

  1. 将其缩小为比例因子,例如在0-256范围内。

  2. 预先计算factor*component为256个元素的数组并“挑选”这些组件。

  3. 我建议的范围是257,因为你可以达到以下因素:

    对于更通用的解决方案(0<=factor<=256):

    void PixelRenderer::applyLight(Uint32& color, Uint32 factor){
        Uint32 alpha=color&0xFF000000;
        Uint32 red=  ((color&0x00FF0000)*factor)>>8;
        Uint32 green= ((color&0x0000FF00)*factor)>>8;
        Uint32 blue=((color&0x000000FF)*factor)>>8;
    
        color=alpha|(red&0x00FF0000)|(green&0x0000FF00)|(blue&0x000000FF);
    }
    

    这是一个可运行的程序,说明了第一个例子:

    #include <stdio.h>
    #include <inttypes.h>
    
    typedef uint32_t Uint32;
    
    Uint32 make(Uint32 alpha,Uint32 red,Uint32 green,Uint32 blue){
        return (alpha<<24)|(red<<16)|(green<<8)|blue;
    }
    
    void output(Uint32 color){
        printf("alpha=%"PRIu32" red=%"PRIu32" green=%"PRIu32" blue=%"PRIu32"\n",(color>>24),(color&0xFF0000)>>16,(color&0xFF00)>>8,color&0xFF);
    }
    
    Uint32 applyLight(Uint32 color, double factor){
        Uint32 alpha=color&0xFF000000;
        Uint32 red=  (color&0x00FF0000)*factor;
        Uint32 green= (color&0x0000FF00)*factor;
        Uint32 blue=(color&0x000000FF)*factor;
    
        return alpha|(red&0x00FF0000)|(green&0x0000FF00)|(blue&0x000000FF);
    }
    
    int main(void) {
        Uint32 color1=make(156,100,50,20);
        Uint32 result1=applyLight(color1,0.9);
        output(result1);
    
        Uint32 color2=make(255,255,255,255);
        Uint32 result2=applyLight(color2,0.1);
        output(result2);
    
        Uint32 color3=make(78,220,200,100);
        Uint32 result3=applyLight(color3,0.05);
        output(result3);
    
        return 0;
    }
    

    预期输出为:

    alpha=156 red=90 green=45 blue=18
    alpha=255 red=25 green=25 blue=25
    alpha=78 red=11 green=10 blue=5
    

答案 3 :(得分:2)

我没有看到其他人提到的一件事是并行化您的代码。至少有两种方法:SIMD指令和多线程。

SIMD instructions(如SSE,AVX等)同时对多个数据执行相同的数学运算。因此,您可以将像素的红色,绿色,蓝色和alpha乘以1条指令中的相同值,如下所示:

vec4 lightValue = vec4(0.5, 0.5, 0.5, 1.0);
vec4 result = vec_Mult(inputPixel, lightValue);

这相当于:

lightValue.red = 0.5;
lightValue.green = 0.5;
lightValue.blue = 0.5;
lightValue.alpha = 1.0;

result.red = inputPixel.red * lightValue.red;
result.green = inputPixel.green * lightValue.green;
result.blue = inputPixel.blue * lightValue.blue;
result.alpha = inputPixel.alpha * lightValue.alpha;

您还可以使用在多个核心上运行的线程将图像切割为切片并同时在多个切片上执行闪电操作。如果您使用的是C ++ 11,则可以使用std::thread来启动多个线程。否则,您的操作系统可能具有线程功能,例如WinThreadsGrand Central Dispatchpthreadsboost threadsThreading Building Blocks等。

您可以将上述两者结合使用,并且具有一次对整个像素进行操作的多线程代码。

如果您想更进一步,可以使用OpenGLOpenCLDirectXMetal,{{{ 3}},Mantle或其他CUDA技术之一。 GPU通常是数百个核心,可以非常快速地并行处理多个磁贴,每个磁贴一次处理整个像素(而不仅仅是通道)。

但更好的选择可能就是根本不写任何代码。很可能有人已经完成了这项工作,你可以利用它。例如,在MacOS上有GPGPUCoreImage框架。在iOS上你也有CoreImage,还有Accelerate。我确信在Windows,Linux和其他可能正在使用的操作系统上都有类似的库。

答案 4 :(得分:1)

  • 不使用位移位器的另一种解决方案是将32 bits uint转换为struct
  • 尝试将您的实施保留在.h包含文件中,以便可以内联
  • 如果您不希望内联实施(请参阅上文),请修改您的applyLight方法以接受像素数组。对于如此小的方法,方法调用开销可能很重要
  • 启用&#34;循环展开&#34;编译器的优化,这将启用SIMD指令的使用

实现:

class brightness {
private:
    struct pixel { uint8_t b, g, r, a; };
    float factor;

    static inline void apply(uint8_t& p, float f) {
        p = max(min(int(p * f), 255),0);
    }

public:
    brightness(float factor) : factor(factor) { }

    void apply(uint32_t& color){
        pixel& p = (pixel&)color;

        apply(p.b, factor);
        apply(p.g, factor);
        apply(p.r, factor);
    }
};

使用查找表实现(使用&#34时循环较慢;循环展开&#34;):

class brightness {

    struct pixel { uint8_t b, g, r, a; };

    uint8_t table[256];

public:
    brightness(float factor) {
        for(int i = 0; i < 256; i++)
            table[i] = max(min(int(i * factor), 255), 0);
    }

    void apply(uint32_t& color){
        pixel& p = (pixel&)color;

        p.b = table[p.b];
        p.g = table[p.g];
        p.r = table[p.r];
    }
};




// usage
brightness half_bright(0.5);
uint32_t pixel = 0xffffffff;
half_bright.apply(pixel);