SSE图像算法优化体系八:自然饱和度(Vibrance)算法的效仿实现及其SSE优化(附源码,可看成SSE图像入门,Vibrance算法也不过用以简单的肤色调整)。

  Vibrance这多少个单词搜索翻译一般震荡,抖动或者是响当当、活力,不过官方的词汇里还一直未出现了本饱和度这些词,也无通晓就底Adobe粤语翻译人士怎么会这样处理。但是大家省PS对之力量的解说:

       Vibrance: Adjusts the
saturation so that clipping is minimized as colors approach full
saturation. This setting changes the saturation of all lower-saturated
colors with less effect on the higher-saturated colors. Vibrance also
prevents skin tones from becoming oversaturated.

     
 确实是暨饱和度有关的,这样懂闽南语的翻译反而却合理,那么只好怪Adobe的开发者为何让那些力量于个名为Vibrance了。

     
 闲话不多说了,其实当饱和度也是如今几单版的PS才面世的效率,在调试有些图片的时刻会时有暴发科学的功能,也可看做简单的肤色调整的一个算法,比如下边这号小姐,用当饱和度即可以叫其失血过多,也得让他肤色红晕。

     
 图片 1   
 图片 2   
 图片 3

                                         
   原图                                                                
                                 面色苍白                              
                                                    肤色红晕一点

     
 那么这些算法的内在是哪些兑现的吗,我未曾仔细的夺研讨他,可是在开源软件PhotoDemon-master(开源地址:https://github.com/tannerhelland/PhotoDemon,visual
basic
6.0的小说,我之最好易)提供了一个不怎么相似的成效,我们贴发出他针对性转移效果的部分注释:

'***************************************************************************
'Vibrance Adjustment Tool
'Copyright 2013-2017 by Audioglider
'Created: 26/June/13
'Last updated: 24/August/13
'Last update: added command bar
'
'Many thanks to talented contributer Audioglider for creating this tool.
'
'Vibrance is similar to saturation, but slightly smarter, more subtle. The algorithm attempts to provide a greater boost
' to colors that are less saturated, while performing a smaller adjustment to already saturated colors.
'
'Positive values indicate "more vibrance", while negative values indicate "less vibrance"
'
'All source code in this file is licensed under a modified BSD license. This means you may use the code in your own
' projects IF you provide attribution. For more information, please visit http://photodemon.org/about/license/
'
'***************************************************************************

 其中的讲述和PS官方文档的描述来类似之处。

   大家在粘贴起他的着力代码:

     For x = initX To finalX
        quickVal = x * qvDepth
        For y = initY To finalY
            'Get the source pixel color values
            r = ImageData(quickVal + 2, y)
            g = ImageData(quickVal + 1, y)
            b = ImageData(quickVal, y)

            'Calculate the gray value using the look-up table
            avgVal = grayLookUp(r + g + b)
            maxVal = Max3Int(r, g, b)

            'Get adjusted average
            amtVal = ((Abs(maxVal - avgVal) / 127) * vibranceAdjustment)

            If r <> maxVal Then
                r = r + (maxVal - r) * amtVal
            End If
            If g <> maxVal Then
                g = g + (maxVal - g) * amtVal
            End If
            If b <> maxVal Then
                b = b + (maxVal - b) * amtVal
            End If

            'Clamp values to [0,255] range
            If r < 0 Then r = 0
            If r > 255 Then r = 255
            If g < 0 Then g = 0
            If g > 255 Then g = 255
            If b < 0 Then b = 0
            If b > 255 Then b = 255

            ImageData(quickVal + 2, y) = r
            ImageData(quickVal + 1, y) = g
            ImageData(quickVal, y) = b
        Next
    Next

  很简短的算法,先要出每个像素RGB分量的优秀酷价值和平均值,然后求两者之差,之后因输入调节量求出调整量。

     
 VB的语法有些人想必无熟练,我不怎么开点转翻译成C的代码如下:

    float VibranceAdjustment = -0.01 * Adjustment;        //       Reverse the vibrance input; this way, positive values make the image more vibrant.  Negative values make it less vibrant.
    for (int Y = 0; Y < Height; Y++)
    {
        unsigned char * LinePS = Src + Y * Stride;
        unsigned char * LinePD = Dest + Y * Stride;
        for (int X = 0; X < Width; X++)
        {
            int Blue = LinePS[0],    Green = LinePS[1],    Red = LinePS[2];
            int Avg = (Blue + Green + Green + Red) >> 2;
            int Max = IM_Max(Blue, IM_Max(Green, Red));
            float AmtVal = (abs(Max - Avg) / 127.0f) * VibranceAdjustment;                        //    Get adjusted average
            if (Blue != Max)    Blue += (Max - Blue) * AmtVal;
            if (Green != Max)    Green += (Max - Green) * AmtVal;
            if (Red != Max)    Red += (Max - Red) * AmtVal;
            LinePD[0] = IM_ClampToByte(Blue);
            LinePD[1] = IM_ClampToByte(Green);
            LinePD[2] = IM_ClampToByte(Red);
            LinePS += 3;
            LinePD += 3;
        }
    }

  那个的结果跟PS的是相比较相近的,最起码趋势是杀接近的,不过细节要未相同,但是好判明的是趋势是针对性的,假如你早晚要复制PS的结果,我指出你花点时间变更中的一部分常数或者总计办法省。应该会来获取,国内曾经有人摸出来了。

     
大家紧要讲下这么些算法的优化及其SSE实现,特别是SSE版本代码是本文的要害。

      第一步优化,去破除不必要总括和除法,很扎眼,这同样词是本段代码中耗时较为强烈的组成部分

        float AmtVal = (abs(Max –
Avg) / 127.0f) * VibranceAdjustment;     

  /127.0f可以优化为乘法,同时注意VibranceAdjustment在里边未变换,可以把他们结成及循环的顶外层,即改变吧:

      float VibranceAdjustment = -0.01 * Adjustment / 127.0f;

  再留意abs里之参数, 马克斯(Max) –
Avg,这起必要取相对值吗,最丰富价值难道会于平均值小,浪费时间,最终移吗:

      float AmtVal = (Max - Avg) * VibranceAdjustment;

    这是浮点版本的概括优化,如若不勾选编译器的SSE优化,直接下FPU,对于一副3000*2000底24位图像耗时在I5的一样宝机器上运行用时大约70纳秒,但当下不是至关重要。

  大家来设想某些近似和一向优化。

     
 第一大家管/127改也/128,这基本未影响功效,同时Adjustment默认的界定吗[-100,100],把其呢线性扩充一点,比如扩充1.28倍增,扩张到[-128,128],这样以终极大家五遍性移位,减弱中间的损失,大概的代码如下:

int IM_VibranceI(unsigned char *Src, unsigned char *Dest, int Width, int Height, int Stride, int Adjustment)
{
    int Channel = Stride / Width;
    if ((Src == NULL) || (Dest == NULL))                return IM_STATUS_NULLREFRENCE;
    if ((Width <= 0) || (Height <= 0))                    return IM_STATUS_INVALIDPARAMETER;
    if (Channel != 3)                                    return IM_STATUS_INVALIDPARAMETER;

    Adjustment = -IM_ClampI(Adjustment, -100, 100) * 1.28;        //       Reverse the vibrance input; this way, positive values make the image more vibrant.  Negative values make it less vibrant.
    for (int Y = 0; Y < Height; Y++)
    {
        unsigned char *LinePS = Src + Y * Stride;
        unsigned char *LinePD = Dest + Y * Stride;
        for (int X = 0; X < Width; X++)
        {
            int Blue, Green, Red, Max;
            Blue = LinePS[0];    Green = LinePS[1];    Red = LinePS[2];
            int Avg = (Blue + Green + Green + Red) >> 2;
            if (Blue > Green)
                Max = Blue;
            else
                Max = Green;
            if (Red > Max)
                Max = Red;
            int AmtVal = (Max - Avg) * Adjustment;                                //    Get adjusted average
            if (Blue != Max)    Blue += (((Max - Blue) * AmtVal) >> 14);
            if (Green != Max)    Green += (((Max - Green) * AmtVal) >> 14);
            if (Red != Max)        Red += (((Max - Red) * AmtVal) >> 14);
            LinePD[0] = IM_ClampToByte(Blue);
            LinePD[1] = IM_ClampToByte(Green);
            LinePD[2] = IM_ClampToByte(Red);
            LinePS += 3;
            LinePD += 3;
        }
    }
    return IM_STATUS_OK;
}

  这样优化后,同样大小的图像算法用时35皮秒,效果以及浮点版本的骨干无啥区别。

     
 最终大家最紧要来讲讲SSE版本的优化。

  
对于这种单像素点、和世界无关之图像算法,为了能拔取SSE提高程序速度,一个中坚的步骤就是是将各级颜色分量分离也单身的连年的变量,对于24各项图像,我们通晓图像在内存中的布局也:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
B1 G1 R1 B2 G2 R2 B3 G3 R3 B4 G4 R4 B5 G5 R5 B6 G6 R6 B7 G7 R7 B8 G8 R8 B9 G9 R9 B10 G10 R10 B11 G11 R11 B12 G12 R12 B13 G13 R13 B14 G14 R14 B15 G15 R15 B16 G16 R16

 

       

      大家用将其成为:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
B1 B2 B3 B4 B4 B5 B7 B8 B9 B10 B11 B12 B13 B14 B15 B16 G1 G2 G3 G4 G5 G6 G7 G8 G9 G10 G11 G12 G13 G14 G15 G16 R1 R2 R3 R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16

 

     

   
 处理完毕后大家而如将她们过来至原有之BGR布局。

   
 为了促成这一个成效,我参考了采石工大侠的有关代码,分享如下:

     我们先行贴下代码:

    Src1 = _mm_loadu_si128((__m128i *)(LinePS + 0));
    Src2 = _mm_loadu_si128((__m128i *)(LinePS + 16));
    Src3 = _mm_loadu_si128((__m128i *)(LinePS + 32));

    Blue8 = _mm_shuffle_epi8(Src1, _mm_setr_epi8(0, 3, 6, 9, 12, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1));
    Blue8 = _mm_or_si128(Blue8, _mm_shuffle_epi8(Src2, _mm_setr_epi8(-1, -1, -1, -1, -1, -1, 2, 5, 8, 11, 14, -1, -1, -1, -1, -1)));
    Blue8 = _mm_or_si128(Blue8, _mm_shuffle_epi8(Src3, _mm_setr_epi8(-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 4, 7, 10, 13)));

    Green8 = _mm_shuffle_epi8(Src1, _mm_setr_epi8(1, 4, 7, 10, 13, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1));
    Green8 = _mm_or_si128(Green8, _mm_shuffle_epi8(Src2, _mm_setr_epi8(-1, -1, -1, -1, -1, 0, 3, 6, 9, 12, 15, -1, -1, -1, -1, -1)));
    Green8 = _mm_or_si128(Green8, _mm_shuffle_epi8(Src3, _mm_setr_epi8(-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, 5, 8, 11, 14)));

    Red8 = _mm_shuffle_epi8(Src1, _mm_setr_epi8(2, 5, 8, 11, 14, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1));
    Red8 = _mm_or_si128(Red8, _mm_shuffle_epi8(Src2, _mm_setr_epi8(-1, -1, -1, -1, -1, 1, 4, 7, 10, 13, -1, -1, -1, -1, -1, -1)));
    Red8 = _mm_or_si128(Red8, _mm_shuffle_epi8(Src3, _mm_setr_epi8(-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 3, 6, 9, 12, 15)));

     
首先,一回性加载48独图像数据到内存,正好停于六个__m128i变量中,同时此外一个这么些好的工作就是48恰好能叫3打点除,也就是说我们圆的加载了16独24员像素,这样虽未碰面油可是生断层,只代表下面48单像素可以和本的48只像素使用相同的不二法门举行拍卖。

     
如达到代码,则Src1遭受保留着:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
B1 G1 R1 B2 G2 R2 B3 G3 R3 B4 G4 R4 B5 G5 R5 B6

 

 

      Src2碰着保留着:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
G6 R6 B7 G7 R7 B8 G8 R8 B9 G9 R9 B10 G10 R10 B11 G11

 

 

  Src3受之数则也:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
R11 B12 G12 R12 B13 G13 R13 B14 G14 R14 B15 G15 R15 B16 G16 R16

 

 

    为了达到我们的目标,我们即将动用SSE中所向披靡的shuffle指令了,假使可以把shuffle指令运用的全,可以得很多异常风趣的效用,有如鸠摩智的略无相功一样,可以催动拈花指发、袈裟服魔攻等等,成就世间能同自我鸠摩智打成平成的从未有过五只人一致的伟业。哈哈,说远了。

   
 简单的知道shuffle指令,就是用__m128i变量内之一一数据论指定的各种举行再一次布置,当然是布阵不必然要了用原来的数量,也堪又某些数据,或者某些地方多服从,比如在履行下就漫长指令

    Blue8 = _mm_shuffle_epi8(Src1,
_mm_setr_epi8(0, 3, 6, 9, 12, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1));

   Blue8中的多少吧:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
B1 B2 B3 B4 B5 B6 0 0 0 0 0  0  0  0  0
_mm_setr_epi8指令的参数顺序可能更适合于我们常用的从左到右的理解习惯,其中的某个参数如果不在0和15之间时,则对应位置的数据就是被设置为0。

 
 可以看出进展上述操作后Blue8的签6单字节已经入我们的需求了。

   以扣押代码的下同样句:

        Blue8 = _mm_or_si128(Blue8, _mm_shuffle_epi8(Src2, _mm_setr_epi8(-1, -1, -1, -1, -1, -1, 2, 5, 8, 11, 14, -1, -1, -1, -1, -1)));

  这句之继半片段及后面的近乎,只是内的常数不同,由_mm_shuffle_epi8(Src2,
_mm_setr_epi8(-1, -1, -1, -1, -1, -1, 2, 5, 8, 11, 14, -1, -1, -1,
-1, -1))得到的临时数据为:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
0 0 0 0 0 0 B7 B8 B9 B10 B11 0 0 0 0 0

 

 

   
 假诺拿那临时结果跟前的Blue8实行或者操作依旧一贯开展加操作,新的Blue8变量则为:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
B1 B2 B3 B4 B5 B6 B7 B8 B9 B10 B11 0 0 0 0 0

 

 

     
最终就等同句子和Blue8相关的代码为:

Blue8 = _mm_or_si128(Blue8, _mm_shuffle_epi8(Src3, _mm_setr_epi8(-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 4, 7, 10, 13)));

  前面的shuffle临时之取得的变量为:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
0 0 0 0 0 0 0 0 0 0 0 B12 B13 B14 B15 B16

 

 

   
 再一次和前的Blue8结果开展或者操作得到最后之结果:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
B1 B2 B3 B4 B5 B6 B7 B8 B9 B10 B11 B12 B13 B14 B15 B16

 

 

   
 对于格林和Red分量,处理的道与步骤是同等的,只是由于地点不同,每一遍举行shuffle操作的常数有所不同,但原理完全相同。

  如若领悟了由BGRBGRBGR
—》变为了BBBGGGRRR这样的形式之原理后,那么由BBBGGGRRR–>变为BGRBGRBGR的道理就充裕浅显了,这里不赘述,直接贴发出代码:

    Dest1 = _mm_shuffle_epi8(Blue8, _mm_setr_epi8(0, -1, -1, 1, -1, -1, 2, -1, -1, 3, -1, -1, 4, -1, -1, 5));
    Dest1 = _mm_or_si128(Dest1, _mm_shuffle_epi8(Green8, _mm_setr_epi8(-1, 0, -1, -1, 1, -1, -1, 2, -1, -1, 3, -1, -1, 4, -1, -1)));
    Dest1 = _mm_or_si128(Dest1, _mm_shuffle_epi8(Red8, _mm_setr_epi8(-1, -1, 0, -1, -1, 1, -1, -1, 2, -1, -1, 3, -1, -1, 4, -1)));

    Dest2 = _mm_shuffle_epi8(Blue8, _mm_setr_epi8(-1, -1, 6, -1, -1, 7, -1, -1, 8, -1, -1, 9, -1, -1, 10, -1));
    Dest2 = _mm_or_si128(Dest2, _mm_shuffle_epi8(Green8, _mm_setr_epi8(5, -1, -1, 6, -1, -1, 7, -1, -1, 8, -1, -1, 9, -1, -1, 10)));
    Dest2 = _mm_or_si128(Dest2, _mm_shuffle_epi8(Red8, _mm_setr_epi8(-1, 5, -1, -1, 6, -1, -1, 7, -1, -1, 8, -1, -1, 9, -1, -1)));

    Dest3 = _mm_shuffle_epi8(Blue8, _mm_setr_epi8(-1, 11, -1, -1, 12, -1, -1, 13, -1, -1, 14, -1, -1, 15, -1, -1));
    Dest3 = _mm_or_si128(Dest3, _mm_shuffle_epi8(Green8, _mm_setr_epi8(-1, -1, 11, -1, -1, 12, -1, -1, 13, -1, -1, 14, -1, -1, 15, -1)));
    Dest3 = _mm_or_si128(Dest3, _mm_shuffle_epi8(Red8, _mm_setr_epi8(10, -1, -1, 11, -1, -1, 12, -1, -1, 13, -1, -1, 14, -1, -1, 15)));

  主题依然那么些常数的选项。

     
以上是处理的首先步,看上去是代码很多,实际上他们之举办时相当抢之,3000*2000之觊觎是拆分和联过程也便大致2ms。

     
当然是因为字节数据类的表达范围大有限,除了少有的几乎单鲜的操作会对字节类型直接处理外,比如本例的丘RGB的马克斯值,就得一直用底的SIMD指令实现:

Max8 = _mm_max_epu8(_mm_max_epu8(Blue8, Green8), Red8);

     
很其他多计都是无能为力直接以如此的限定外展开了,因而便闹必要将数据类型扩充,比如扩充及short类型或者int/float类型。

     
在SSE里开展这样的操作也是十分简单的,SSE提供了大气底数据类型转换的函数和下令,比如有byte扩展及short,则可用_mm_unpacklo_epi8和_mm_unpackhi_epi8配合zero来实现:

BL16 = _mm_unpacklo_epi8(Blue8, Zero);
BH16 = _mm_unpackhi_epi8(Blue8, Zero);

  其中

Zero = _mm_setzero_si128();

   很有意思的操作,比如_mm_unpacklo_epi8凡将点滴个__m128i之低8各项交错布置形成一个初的128各项数据,假诺中间一个参数为0,则就是将其余一个参数的低8独字节无损的扩大为16位了,以上述BL16啊条例,其里面布局也:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
B1 0 B2 0 B3 0 B3 0 B4 0 B5 0 B6 0 B7 0

 

 

  假如我们得开展以int范围外展开总括,则还需更加壮大,此时好使_mm_unpackhi_epi16/_mm_unpacklo_epi16匹zero继续拓展扩大,这样一个Blue8变量需要4单__m128i
int范围的多寡来抒发。

     
好,说道这里,我们后续看大家C语言里之当即句:

  int Avg = (Blue + Green + Green + Red) >> 2;

  可以视,这里的乘除是无能为力再byte范围外完成的,中间的Blue

  • 格林 + 格林 +
    Red在大部意况下还谋面超越255而绝小于255*4,,由此大家得扩张数据及16各,按上述方法,对Blue8\Green8\Red8\马克斯8开展扩展,如下所示:

      BL16 = _mm_unpacklo_epi8(Blue8, Zero);
      BH16 = _mm_unpackhi_epi8(Blue8, Zero);
      GL16 = _mm_unpacklo_epi8(Green8, Zero);
      GH16 = _mm_unpackhi_epi8(Green8, Zero);
      RL16 = _mm_unpacklo_epi8(Red8, Zero);
      RH16 = _mm_unpackhi_epi8(Red8, Zero);
      MaxL16 = _mm_unpacklo_epi8(Max8, Zero);
      MaxH16 = _mm_unpackhi_epi8(Max8, Zero);
    

  这总结Avg就回到渠道成了:

     AvgL16 = _mm_srli_epi16(_mm_add_epi16(_mm_add_epi16(BL16, RL16), _mm_slli_epi16(GL16, 1)), 2);
     AvgH16 = _mm_srli_epi16(_mm_add_epi16(_mm_add_epi16(BH16, RH16), _mm_slli_epi16(GH16, 1)), 2);

  中间两个格林相加是由此移动或直接相加对快没有啥影响之。

     
 接下来的优化则是本例的一个风味有了。我们来详细分析。

     
 大家精晓,SSE对于跳转是雅不协调之,他很擅长连串化处理一个事情,虽然他提供了众多相比指令,可是多状态下复杂的跳转SSE还是无为力,对于本例,境况于新鲜,假如如使SSE的于指令也是可一直促成的,实现的计时,使用于指令得到一个Mask,Mask中符合于结实的值会为FFFFFFFF,不合乎的为0,然后将此Mask和后要统计的有值举办And操作,由于和FFFFFFFF举办And操作不会晤变动操作数本身,和0举行And操作则变为0,在很多状态下,就是无你符合条件与否,都进展末端的乘除,只是不符合条件的乘除不会师潜移默化结果,这种总括可能相会失效SSE优化的一部分提速效果,这多少个将要具体境况具体分析了。

     
注意观看本例的代码,他的原意是只要尽酷价值和某个分量的价值未同等,则开展末端的调整操作,否则不开展调剂。可前面的调操作中生太老价值减去该分量的操作,也便代表假若尽特别价值与该分量相同,两者相减则为0,调整量此时啊为0,并无影响结果,也不怕分外给无调节,因而,把此条件判断失丢,并无会面潜移默化结果。同时考虑到实际情状,最特别价值在多情呢止会暨某个一个份额相同,也就是说只来1/3底票房价值不执跳转后底语句,在本例中,跳反后底代码执行复杂度并无高,去丢这多少个规则判断用扩展并代码所消耗的性能与压缩3个判断的时空都在一个水平及了,因而,完全好去这个判断语句,这样便万分适合于SSE实现了。

  接着分析,由于代码中生出((马克斯 –
Blue) * AmtVal) >> 14,其中AmtVal
= (Max – Avg) * Adjustment,展开就为:  ((马克斯(Max) – Blue) * (Max – Avg) *
Adjustment)>>14;这三独数据相乘很可怜程度及会压倒short所可以表明的克,因而,我们尚待对地点的16各数据举办扩充,增添至32个,这样虽差不多矣成百上千指令,那么来无发出非需扩张的章程啊。经过一番想,我指出了下述解决方案:

   
跨急迅指数模糊算法的贯彻与优化(10000*10000当100ms左右实现 一和遭遇,我当著作最终提到了极点的一个限令:_mm_mulhi_epi16(a,b),他能一回性处理8独16位数据,其统计结果一定给对于(a*b)>>16,但此处充足明a和b必须是short类型所能表明的限量。

       注意大家的这多少个表明式:

              ((Max – Blue) * (Max –
Avg) * Adjustment)>>14

   
   首先,大家以他恢弘为移动16各项之结果,变为如下:

         ((Max – Blue) * 4 * (Max –
Avg) * Adjustment)>>16

     
Adjustment大家都将他限定在了[-128,128]中间,而(马克斯(Max) –
Avg)理论及之极充足价值吗255 –
85=170,(即RGB分量有一个凡255,其他的且为0),最小值为0,由此,两者在独家范围外的战表未会面胜出short所能表达的限制,而(马克斯-Blue)的优秀丰硕价值为255,最小值为0,在就以4吧当short类型所能发挥的范围外。所以,下一样步你们知道了为?

     
 经过上述分析,上边就四行C代码可由下述SSE函数实现:

    int AmtVal = (Max - Avg) * Adjustment;                                //    Get adjusted average
    if (Blue != Max)    Blue += (((Max - Blue) * AmtVal) >> 14);
    if (Green != Max)    Green += (((Max - Green) * AmtVal) >> 14);
    if (Red != Max)        Red += (((Max - Red) * AmtVal) >> 14);

  对应的SSE代码为:

    AmtVal = _mm_mullo_epi16(_mm_sub_epi16(MaxL16, AvgL16), Adjustment128);
    BL16 = _mm_adds_epi16(BL16, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(MaxL16, BL16), 2), AmtVal));
    GL16 = _mm_adds_epi16(GL16, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(MaxL16, GL16), 2), AmtVal));
    RL16 = _mm_adds_epi16(RL16, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(MaxL16, RL16), 2), AmtVal));

    AmtVal = _mm_mullo_epi16(_mm_sub_epi16(MaxH16, AvgH16), Adjustment128);
    BH16 = _mm_adds_epi16(BH16, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(MaxH16, BH16), 2), AmtVal));
    GH16 = _mm_adds_epi16(GH16, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(MaxH16, GH16), 2), AmtVal));
    RH16 = _mm_adds_epi16(RH16, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(MaxH16, RH16), 2), AmtVal));

  最终一步就是是用这个16各项的多寡重复转移为8各之,注意原始代码中暴发Clamp操作,这些操作实际是单耗时的经过,而SSE天然的所有抗饱和的函数。

  Blue8 = _mm_packus_epi16(BL16, BH16);
  Green8 = _mm_packus_epi16(GL16, GH16);
  Red8 = _mm_packus_epi16(RL16, RH16);

 _mm_packus_epi16这个的用法和含义自己去MSDN搜索一下吧,实在是懒得解释了。

   最后优化速度:5ms。

   来单速度比:

版本 VB6.0 C++,float优化版本 C++定点版 C++/SSE版
速度 400ms 70ms 35ms 5ms

 

     

  下边的VB6.0的耗时凡是原作者的代码编译后底实施进度,假如自身自己失去用VB6.0去优化他的说话,有信念会就70ms以内的。

  但不管怎么样,SSE优化的速度提升是远大的。

结论:

       简单的剖析了本来饱和度算法的贯彻,分享了那几个SSE实现之进程,对于那多少个刚刚接触SSE,想做图像处理的恋人出肯定之帮忙。

     
  源代码下载地址:http://files.cnblogs.com/files/Imageshop/Vibrance.rar

       
写的真正好累,休息去了,觉得对您有效之求于自家购买杯苦味酒或咖啡吧。

图片 4