2D模式下的alpha混合

 

alpha混合是一种常见的颜色处理,是把源点的颜色值和目标点的颜色值按照一定的算法进行运算,得到一个透明的效果.

alpha混合的基本公式:

result = ALPHA * srcPixel + ( 1 - ALPHA ) * destPixel
其中:

  ALPHA:0到1之间的一个数,表示混合时的透明程度,为0的时候结果就是目标点的原值,为1的时候是源点的原值
  srcPixel:源点颜色值
  destPixel:目标点颜色值
  result:结果,将会赋给目标点

  这个是最基本的公式,但是我们在写程序的时候可以做一些优化.公式里面有两个乘号,乘法消耗的时钟周期要多得多,我们要想方法去掉它.做一个变换.

alpha混合的改进公式1:
result = ALPHA * ( srcPixel - destPixel ) + destPixel

  然后我们也看到ALPHA是一个浮点数,我们可以把它转换成整数,因为一种颜色最多占8bit,所以ALPHA的值也最多到256,那我们把ALPHA的值先乘上256,然后运算的时候再除以256就得到下面的公式:

alpha混合的改进公式2:
result = ALPHA * ( srcPixel - destPixel )/256 + destPixel
其中:

  ALPHA是一个0到256的数

  有了这个公式,我们的第一份code也就出来了.

alpha混合代码1:
//16位565格式
  i = height;   // 混合矩形高度
  do
  { 
    j = width;  // 混合矩形宽度
    do 
    { 
      sTemp = *((WORD*)lpSour);  // 源点颜色值,16位--WORD类型,(lpSour和lpDest都是BYTE*)
      if ( sTemp != sColorKey )   // 不是colorkey
      { 
        dTemp = *((WORD*)lpDest); // 目标点

        sb = sTemp & 0x1f;     // 蓝色分量
        db = dTemp & 0x1f; 

        sg = (sTemp >> 5) & 0x3f; // 绿色分量
        dg = (dTemp >> 5) & 0x3f; 

        sr = (sTemp >> 11) & 0x1f;// 红色分量
        dr = (dTemp >> 11) & 0x1f;

        blue = (ALPHA * (sb - db) >> 8) + db;  // 按照改进公式2运算三个分量
        green = (ALPHA * (sg - dg) >> 8) + dg; 
        red = (ALPHA * (sr - dr) >> 8) + dr; 
        *((WORD*)lpDest) = blue | (green << 5) | (red << 11); // 565格式生成结果并赋给目标点
      } 
      lpDest += 2; // 下一个点,16位占2个字节
      lpSour += 2; 
    }while ( --j > 0 ); 
    lpDest += dPadding; // 下一行
    lpSour += sPadding;
  }while (--i > 0);

  到此alpha混合也就差不多了,但是不得不说说MMX版本的alpha混合.上面的方式进行alpha混合的时候效率不高,而MMX使用64位的MMX寄存器,一次操作8(byte)/4(word)/2(dword)个数据单元,也就是所谓的"单指令多数据SIMD结构".这样我们就可以一次处理几个点.下面的代码是写给16位565模式的.
  因为是16位模式,那么我们可以一次处理4个点,但是因为编译器不会主动的生成MMX代码,所以我们使用inline asm的方式进行.因为我们的点是定义成无符号的word型,不能出现负数,所以我们要修改我们的alpha混合公式:

alpha混合mmx版公式:
result = ( ALPHA * ( ( srcPixel+ 64 ) - destPixel) ) / 256 + destPixel- (ALPHA / 4)


这个公式是这样来的:因为16位模式下每个点的分量最多6位,换成10进制也就是63,那么我们在srcPixel上面加上64然后再减去destPixel,就不会出现负数了.加了之后当然要减回来,那这个公式就是这样的:

result = ( ALPHA * ( ( srcPixel+ 64 -64) - destPixel) ) / 256 + destPixel

然后把紫色的-64提出来也就得到了上面的公式了.

  好了我们可以动手写code了,看起来的code应该如下:

alpha混合code2:

  MASKRED = 0xF800F800F800F800;   // 三种颜色的掩码的64位扩展
  MASKGREEN = 0x07E007E007E007E0;
  MASKBLUE = 0x001F001F001F001F;

  __int64 ALPHA64, COLORKEY64, ALPHABY4;
  __int64 MASKRED, MASKGREEN, MASKBLUE;
  __int64 ADD64 = 0x0040004000400040;

  _asm
  {
    movd mm2,ALPHA         // ALPHA值放入mm2
    punpcklwd mm2,mm2        // mm2 -> 0000 0000 00aa 00aa
    punpckldq mm2,mm2        // mm2 - 00aa 00aa 00aa 00aa,生成alpha的64位扩展
    movq ALPHA64,mm2        // 结果放入到 ALPHA64

    psrlw mm2, 2          // 每个ALPHA 除以4
    movq ALPHABY4,mm2        // 放到 ALPHABY4

    movd mm4,ColorKey        // ColorKey -> mm4
    punpcklwd mm4,mm4        // mm4 -> 0000 0000 cccc cccc
    punpckldq mm4,mm4        // mm4 -> cccc cccc cccc cccc,生成Colorkey的64位扩展
    movq COLORKEY64,mm4 ;      // 结果放到 COLORKEY64
  }

  i = height;
  do
  {
    j = width/4;
    
    // 两行写在一起表示假设他们同时在uv管道里面执行

    _asm
    {
      push edi           // 保存edi和esi
      push esi

      mov edi,lpDest        // edi指向dest缓冲区
      mov esi,lpSour;       // esi指向sour缓冲区

SPAN_RUN_565: 
      movq mm7,[edi]        // dest的8 bytes 到 mm7--4个dest点到mm7
      movq mm6,[esi]        // sour的8 bytes 到 mm6--4个sour点到mm6

      movq mm2,ALPHA64       // ALPHA64 -> mm2
      movq mm0,mm7         // 红色 - 复制 mm7 到 mm0,目标点

      pand mm0,MASKRED       // 红色 - 与上红色掩码 -> [0r00 0r00 0r00 0r00],目标点
      movq mm1,mm6         // 红色 - 复制 mm6 到 mm1,源点

      pand mm1,MASKRED       // 红色 - 与上红色掩码 -> [0r00 0r00 0r00 0r00],源点
      psrlw mm0,11         // 红色 - 右移 11 位 -> [000r 000r 000r 000r],目标点

      movq mm5,mm7         // 绿色 - 复制 mm7 到 mm5,目标点
      psrlw mm1,11         // 红色 - 右移 11 位 -> [000r 000r 000r 000r],源点

      paddw mm1, ADD64      // 红色 - 源点加上64

      movq mm3,mm6         // 绿色 - 复制 mm6 到 mm3,源点
      psubsw mm1,mm0        // 红色 - 减去目标点

      pand mm5,MASKGREEN      // 绿色 - 与上绿色掩码 -> [00g0 00g0 00g0 00g0],目标点
      pmullw mm1,mm2        // 红色 - 乘以ALPHA

      pand mm3,MASKGREEN      // 绿色 - 与上绿色掩码 -> [00g0 00g0 00g0 00g0],源点
      psrlw mm5,5         // 红色 - 右移 5 位 -> [000g 000g 000g 000g],目标点

      psrlw mm3,5         // 红色 - 右移 5 位 -> [000g 000g 000g 000g],源点
      nop             // 空操作 - 指令配对


      paddw mm3, ADD64      // 绿色 - 源点加上64


      psrlw mm1,8         // 红色 - 除以 256 (右移8位)
      psubsw mm3,mm5        // 绿色 - 减去目标点

      pmullw mm3,mm2        // 绿色 - 乘以ALPHA
      paddw mm1,mm0        // 红色 - 加上目标点

      psubw mm1, ALPHABY4     // 红色 - 减去alpha的四分之一

      psllw mm1,11         // 红色 - 左移11位,还原[0r00 0r00 0r00 0r00]
      movq mm0,mm7         // 蓝色 - 复制 mm7 到 mm0,目标点

      pand mm0, MASKBLUE      // 蓝色 - 与上蓝色掩码 -> [000b 000b 000b 000b],目标点 
      psrlw mm3,8         // 绿色 - 除以 256 (右移8位)

      paddw mm3,mm5        // 绿色 - 加上目标点
      movq mm4, mm6        // 蓝色 - 复制 mm6 到 mm4,源点

      psubw mm3, ALPHABY4     // 绿色 - 减去alpha的四分之一

      pand mm4, MASKBLUE     // 蓝色 - 与上蓝色掩码 -> [000b 000b 000b 000b],源点
      psllw mm3,5         // 绿色 - 左移5位,还原[00g0 00g0 00g0 00g0]

      paddw mm4, ADD64      // 蓝色 - 加上64

      psubsw mm4,mm0        // 蓝色 - 减去目标点
      por mm1,mm3         // 组合红色和绿色分量

      pmullw mm4,mm2        // 蓝色 - 乘以alpha
      movq mm3,COLORKEY64     // COLORKEY64 -> mm3

      psrlw mm4,8         // 蓝色 - 除以256(右移8位)
      pcmpeqw mm3,mm6       // 比较 colorKey 和原来的sour,如果相等,则相应的mm3的位为1

      paddw mm4,mm0        // 蓝色 - 加上目标点
      movq mm5,mm3         // mm3 -> mm5

      psubw mm4, ALPHABY4     // 蓝色 - 减去alpha的四分之一

      por mm1,mm4         // 合成3种颜色
      pand mm5,mm7         // mm5与上目标点 - 取出源点是colorkey的点的目标点的颜色值

      pandn mm3,mm1        // mm3取反然后与上mm1,mm1放的是运算结果,这样就把源点是colorkey的点的结果清0

      por mm3,mm5         // mm3或上mm5,一个是源点是colorkey的点的目标点一个是源点不是colorkey的结果,两个                           // 对立位都是0,这样就组合乘一个结果点

      movq [edi],mm3         // 最终结果放回目标点

      add edi,8          // 下一个目标点,一次64 bits = 8 bytes
      mov eax,dword ptr [j]    // j -> eax

      add esi,8          // 下一个源点
      sub eax,1          // j--

      mov dword ptr [lpDest], edi // 保存lpDest
      mov dword ptr [j],eax    // 赋回 j

      mov dword ptr [lpSour], esi // 保存lpSour
      cmp eax,0          // 这一行是不是处理完了

      jg SPAN_RUN_565       // 下一个点

      emms             // 清除mmx寄存器

      pop esi           // 恢复 esi 和 edi
      pop edi 
    }

    lpDest += dbuf;         // 下一行
    lpSour+= sbuf;

  }while (--i > 0);

  看看上面的源代码应该能懂的,我写了详细的注释^-^   关于mmx的指令集,点这里,关于mmx指令的开发规则等等,点这里.和我联系一起讨论点这里 :)

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值