c - 如何使用缩放有效地将 16 位无符号短整型转换为 8 位无符号字符?

标签 c x86 sse simd

我正在尝试使用一些缩放函数将 16 位 unsigned Short 数据转换为 8 位 unsigned char 。目前我正在通过转换为 float 并按比例缩小然后饱和为 8 位来实现此目的。有没有更有效的方法来做到这一点?

int _tmain(int argc, _TCHAR* argv[])
{
    float Scale=255.0/65535.0;

    USHORT sArr[8]={512,1024,2048,4096,8192,16384,32768,65535};
    BYTE bArr[8],bArrSSE[8];        

    //Desired Conventional Method
    for (int i = 0; i < 8; i++)
    {
        bArr[i]=(BYTE)(sArr[i]*Scale);                  
    }

    __m128  vf_scale = _mm_set1_ps(Scale),
            vf_Round = _mm_set1_ps(0.5),                      
            vf_zero = _mm_setzero_ps();         
    __m128i vi_zero = _mm_setzero_si128();

    __m128i vi_src = _mm_loadu_si128(reinterpret_cast<const __m128i*>(&sArr[0]));

    __m128 vf_Src_Lo=_mm_cvtepi32_ps(_mm_unpacklo_epi16(vi_src, _mm_set1_epi16(0)));    
    __m128 vf_Src_Hi=_mm_cvtepi32_ps(_mm_unpackhi_epi16(vi_src, _mm_set1_epi16(0)));    

    __m128 vf_Mul_Lo=_mm_sub_ps(_mm_mul_ps(vf_Src_Lo,vf_scale),vf_Round);   
    __m128 vf_Mul_Hi=_mm_sub_ps(_mm_mul_ps(vf_Src_Hi,vf_scale),vf_Round);   

    __m128i v_dst_i = _mm_packus_epi16(_mm_packs_epi32(_mm_cvtps_epi32(vf_Mul_Lo), _mm_cvtps_epi32(vf_Mul_Hi)), vi_zero);
    _mm_storel_epi64((__m128i *)(&bArrSSE[0]), v_dst_i);

    for (int i = 0; i < 8; i++)
    {       
        printf("ushort[%d]= %d     * %f = %.3f ,\tuChar[%d]= %d,\t SSE uChar[%d]= %d \n",i,sArr[i],Scale,(float)(sArr[i]*Scale),i,bArr[i],i,bArrSSE[i]);
    }

    return 0;
}

请注意,缩放因子可能需要设置为其他值,例如255.0/512.0255.0/1024.0255.0/2048.0,因此任何解决方案都不应该针对 255.0/65535.0< 进行硬编码.

最佳答案

如果代码中的比率是固定的,则可以使用以下算法执行缩放

  1. 将每个字的高字节移至低字节。
    例如。 0x200 -> 0x2, 0xff80 -> 0xff
  2. 如果低字节小于 0x80,则添加偏移量 -1。
    例如。 0x200 -> 偏移量-1,0xff80 -> 偏移量0

第一部分可以通过 _mm_srli_epi16 轻松实现

第二个比较棘手,但它基本上包括获取每个字的 bit7(低字节的较高位),将其复制到整个字,然后取反。

我使用了另一种方法:通过将 vector 与其自身进行比较是否相等,创建了一个值为 -1 的单词 vector 。
然后我隔离了每个源字的 bit7,并将其添加到 -1 字中。

#include <stdio.h>
#include <emmintrin.h>

int main(int argc, char* argv[])
{
    float Scale=255.0/65535.0;

    unsigned short sArr[8]={512,1024,2048,4096,8192,16384,32768,65535};
    unsigned char bArr[8], bArrSSE[16];        

    //Desired Conventional Method
    for (int i = 0; i < 8; i++)
    {
        bArr[i]=(unsigned char)(sArr[i]*Scale);                  
    }



    //Values to be converted
    __m128i vi_src = _mm_loadu_si128((__m128i const*)sArr);

    //This computes 8 words (16-bit) that are
    // -1 if the low byte of relative word in vi_src is less than 0x80
    // 0  if the low byte of relative word in vi_src is >= than 0x80

    __m128i vi_off = _mm_cmpeq_epi8(vi_src, vi_src);   //Set all words to -1
    //Add the bit15 of each word in vi_src to each -1 word
    vi_off 
    = _mm_add_epi16(vi_off, _mm_srli_epi16(_mm_slli_epi16(vi_src, 8), 15));

    //Shift vi_src word right by 8 (move hight byte into low byte)
    vi_src = _mm_srli_epi16 (vi_src, 8);  
    //Add the offsets
    vi_src = _mm_add_epi16(vi_src, vi_off); 
    //Pack the words into bytes
    vi_src = _mm_packus_epi16(vi_src, vi_src);

    _mm_storeu_si128((__m128i *)bArrSSE, vi_src);

    for (int i = 0; i < 8; i++)
    {       
        printf("%02x %02x\n",   bArr[i],bArrSSE[i]);
    }

    return 0;
}

关于c - 如何使用缩放有效地将 16 位无符号短整型转换为 8 位无符号字符?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41757747/

相关文章:

x86 - 无溢出的无符号字节总和减少,在 Intel 上使用 SSE2

assembly - 除法导致括号不平衡

c++ - 传递包含 SSE/AVX 值的类型

c - 矩阵的逆不准确

c - 库的 Makefile

c - servicemain 函数仅在服务事件停止时启动

c - C中如何交换链表节点

c - 为什么编译器会生成一个 push/pop 指令对?

c - ALSA:如何在设备列表中找到设备,但在打开它时仍然收到 "No such file or directory"?

c - SIMD以下代码