从预乘浮点RGBA转换为8位RGBA的有效方法?

时间:2014-03-07 08:00:23

标签: c++ c

我正在寻找一种更有效的方法,将RGBA存储为预乘色空间中的双精度转换为8位整数/通道RGBA 非前置色彩空间。这是我图像处理的一个重要成本。

对于一个频道,比方说R,代码看起来像这样:

double temp = alpha > 0 ? src_r / alpha : 0
uint8_t out_r = (uint8_t)min( 255, max( 0, int(temp * 255 + 0.5) ) )

这涉及三个条件,我认为这可以阻止编译器/ CPU优化它。我认为有些芯片,特别是x86_64有专门的双钳位操作,所以从理论上讲,如果没有条件,上面的内容可能是可行的。

是否有一些技术或特殊功能可以使这种转换更快?

我正在使用GCC,如果需要,可以使用C或C ++或内联ASM解决方案。

3 个答案:

答案 0 :(得分:3)

这是一个包含一些代码(未经测试)的大纲。这将一次转换四个像素。这种方法的主要优点是它只需要进行一次除法(不是四次)。分工很慢。但它必须进行转置(AoS到SoA)才能做到这一点。除了将双精度转换为浮点数(需要AVX)之外,它主要使用SSE。

1.) Load 16 doubles
2.) Convert them to floats
3.) Transpose from rgba rgba rgba rgba to rrrr gggg bbbb aaaa
4.) Divide all 4 alphas in one instruction
5.) Round floats to ints
6.) Compress 32-bit to 8-bit with saturation for underflow and overflow
7.) Transpose back to rgba rgba rgba rgba
9.) Write 4 pixels as integers in rgba format

#include <immintrin.h>
double rgba[16];
int out[4];

//load 16 doubles and convert to floats
__m128 tmp1 = _mm256_cvtpd_ps(_mm256_load_pd(&rgba[0]));
__m128 tmp2 = _mm256_cvtpd_ps(_mm256_load_pd(&rgba[4]));
__m128 tmp3 = _mm256_cvtpd_ps(_mm256_load_pd(&rgba[8]));
__m128 tmp4 = _mm256_cvtpd_ps(_mm256_load_pd(&rgba[12]));
//rgba rgba rgba rgba -> rrrr bbbb gggg aaaa
_MM_TRANSPOSE4_PS(tmp1,tmp2,tmp3,tmp4);
//fact = alpha > 0 ? 255.0f/ alpha : 0
__m128 fact = _mm_div_ps(_mm_set1_ps(255.0f),tmp4); 
tmp1 = _mm_mul_ps(fact,tmp1); //rrrr
tmp2 = _mm_mul_ps(fact,tmp2); //gggg
tmp3 = _mm_mul_ps(fact,tmp3); //bbbb    
tmp4 = _mm_mul_ps(_mm_set1_ps(255.0f), tmp4); //aaaa

//round to nearest int
__m128i tmp1i = _mm_cvtps_epi32(tmp1);
__m128i tmp2i = _mm_cvtps_epi32(tmp2);
__m128i tmp3i = _mm_cvtps_epi32(tmp3);
__m128i tmp4i = _mm_cvtps_epi32(tmp4);

//compress from 32bit to 8 bit
__m128i tmp5i = _mm_packs_epi32(tmp1i, tmp2i);
__m128i tmp6i = _mm_packs_epi32(tmp3i, tmp4i);
__m128i tmp7i = _mm_packs_epi16(tmp5i, tmp6i);

//transpose back to rgba rgba rgba rgba
__m128i out16 = _mm_shuffle_epi8(in16,_mm_setr_epi8(0x0,0x04,0x08,0x0c, 0x01,0x05,0x09,0x0d, 0x02,0x06,0x0a,0x0e, 0x03,0x07,0x0b,0x0f));
_mm_store_si128((__m128i*)out, tmp7i);

答案 1 :(得分:2)

好的,这是伪代码,但SSE怎么样呢

const c = (1/255, 1/255, 1/255, 1/255)
floats = (r, g, b, a)
alpha =  (a, a, a, a)
alpha *= (c, c, c, c)
floats /= alpha
ints = cvt_float_to_int(floats)
ints = max(ints, (255, 255, 255, 255))

这是一个实现

void convert(const double* floats, byte* bytes, const int width, const int height, const int step) {
    for(int y = 0; y < height; ++y) {
        const double* float_row = floats + y * width;
        byte*        byte_row  = bytes  + y * step;

        for(int x = 0; x < width; ++x) {
            __m128d src1  = _mm_load_pd(float_row);
            __m128d src2  = _mm_load_pd(float_row + 2);
            __m128d mul   = _mm_set1_pd(255.0f / float_row[3]);
            __m128d norm1 = _mm_min_pd(_mm_set1_pd(255), _mm_mul_pd(src1, mul));
            __m128d norm2 = _mm_min_pd(_mm_set1_pd(255), _mm_mul_pd(src2, mul));
            __m128i dst1 = _mm_shuffle_epi8(_mm_cvtpd_epi32(norm1), _mm_set_epi8(0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,4,0));
            __m128i dst2 = _mm_shuffle_epi8(_mm_cvtpd_epi32(norm2), _mm_set_epi8(0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,4,0,0x80,0x80));
            _mm_store_ss((float*)byte_row, _mm_castsi128_ps(_mm_or_si128(dst1, dst2)));

            float_row += 4;
            byte_row += 4;
        }
    }
}

编辑:在我最初的回答中,我使用了浮点数而不是双精度数,如果有人感兴趣,可以使用以下感谢@Z boson抓住它 - @OP:我不处理alhpa==0如果你想要这个处理,那么你将获得NaN我的解决方案,请使用@Z boson的解决方案。 这是浮动版:

void convert(const float* floats, byte* bytes, const int width, const int height, const int step) {
    for(int y = 0; y < height; ++y) {
        const float* float_row = floats + y * width;
        byte*        byte_row  = bytes  + y * step;

        for(int x = 0; x < width; ++x) {
            __m128 src = _mm_load_ps(float_row);
            __m128 mul = _mm_set1_ps(255.0f / float_row[3]);
            __m128i cvt = _mm_cvtps_epi32(_mm_mul_ps(src, mul));
            __m128i res = _mm_min_epi32(cvt, _mm_set1_epi32(255));
            __m128i dst = _mm_shuffle_epi8(res, _mm_set_epi8(0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,12,8,4,0));
            _mm_store_ss((float*)byte_row, _mm_castsi128_ps(dst));

            float_row += 4;
            byte_row += 4;
        }
    }
}

由于SSE对齐约束,请确保您的输入指针是16字节对齐的,并使用step确保每一行都在对齐的地址处开始,许多库采用这样的step参数,但如果你不需要它,可以通过使用单个循环来简化。

我很快用这个测试并获得了很好的价值:

int main() {
    __declspec(align(16)) double src[] = { 10,100,1000,255, 10,100,20,50 };
    __declspec(align(16)) byte  dst[8];
    convert(src, dst, 2, 1, 16); // dst == { 10,100,255,255 }
    return 0;
}

我现在只有视觉工作室,所以我无法使用gcc的优化器进行测试,但是我得到了一个 x1.8加速用于双倍和x4.5用于浮点数,它可能是少用gcc -O3,但我的代码可以更优化。

答案 2 :(得分:1)

要研究的三件事

  1. 使用着色器使用OpenGL执行此操作。
  2. 使用单指令多数据(SIMD) - 您可能会获得一些并行化。
  3. 使用饱和算术运算(手臂上的SADD和SMULL)