[C#] Bgr24彩色位图转为灰度的Bgr24位图的跨平台SIMD硬件加速向量算法

上一篇文章里,我们讲解了“Bgr24彩色位图转为Gray8灰度位图”算法。本文将探讨“Bgr24彩色位图转为灰度的Bgr24位图”。区别在于目标位图也是Bgr24格式的,只是将像素数据由彩色转为了灰度。这些算法也是跨平台的,同一份源代码,能在 X86及Arm架构上运行,且均享有SIMD硬件加速。

一、标量算法

1.1 算法实现

算法原理与上一篇文章是一样,唯一区别是目标位图的地址计算与写入处理。因为现在对于每一个像素,需要写入3个字节。
源代码如下。

public static unsafe void ScalarDoBatch(byte* pSrc, int strideSrc, int width, int height, byte* pDst, int strideDst) {
   
    const int cbPixel = 3; // Bgr24
    const int shiftPoint = 16;
    const int mulPoint = 1 << shiftPoint; // 0x10000
    const int mulRed = (int)(0.299 * mulPoint + 0.5); // 19595
    const int mulGreen = (int)(0.587 * mulPoint + 0.5); // 38470
    const int mulBlue = mulPoint - mulRed - mulGreen; // 7471
    byte* pRow = pSrc;
    byte* qRow = pDst;
    for (int i = 0; i < height; i++) {
   
        byte* p = pRow;
        byte* q = qRow;
        for (int j = 0; j < width; j++) {
   
            byte gray = (byte)((p[2] * mulRed + p[1] * mulGreen + p[0] * mulBlue) >> shiftPoint);
            q[0] = q[1] = q[2] = gray;
            p += cbPixel; // Bgr24
            q += cbPixel; // Bgr24 store grayscale.
        }
        pRow += strideSrc;
        qRow += strideDst;
    }
}

1.2 基准测试代码

使用 BenchmarkDotNet 进行基准测试。
可以使用上一篇文章的公共函数,写好标量算法的基准测试代码。源代码如下。

[Benchmark(Baseline = true)]
public void Scalar() {
   
    ScalarDo(_sourceBitmapData, _destinationBitmapData, 0);
}

[Benchmark]
public void ScalarParallel() {
   
    ScalarDo(_sourceBitmapData, _destinationBitmapData, 1);
}

public static unsafe void ScalarDo(BitmapData src, BitmapData dst, int parallelFactor = 0) {
   
    int width = src.Width;
    int height = src.Height;
    int strideSrc = src.Stride;
    int strideDst = dst.Stride;
    byte* pSrc = (byte*)src.Scan0.ToPointer();
    byte* pDst = (byte*)dst.Scan0.ToPointer();
    int processorCount = Environment.ProcessorCount;
    int batchSize = 0;
    if (parallelFactor > 1) {
   
        batchSize = height / (processorCount * parallelFactor);
    } else if (parallelFactor == 1) {
   
        if (height >= processorCount) batchSize = 1;
    }
    bool allowParallel = (batchSize > 0) && (processorCount > 1);
    if (allowParallel) {
   
        int batchCount = (height + batchSize - 1) / batchSize; // ceil((double)length / batchSize)
        Parallel.For(0, batchCount, i => {
   
            int start = batchSize * i;
            int len = batchSize;
            if (start + len > height) len = height - start;
            byte* pSrc2 = pSrc + start * strideSrc;
            byte* pDst2 = pDst + start * strideDst;
            ScalarDoBatch(pSrc2, strideSrc, width, len, pDst2, strideDst);
        });
    } else {
   
        ScalarDoBatch(pSrc, strideSrc, width, height, pDst, strideDst);
    }
}

二、向量算法

2.1 算法思路

对于24位转8位灰度,可以使用这种办法: 每次从源位图读取3个向量,进行3-元素组的解交织运算,得到 R,G,B 平面数据。随后使用向量化的乘法与加法,来计算灰度值。最后将存储了灰度值的那一个向量,进行3-元素组的交织运算,便能存储到目标位图。

它与“Bgr24彩色位图转为Gray8灰度位图”向量算法的区别,在于最后需做“3-元素组的交织运算”。

例如 Sse指令集使用的是128位向量,此时1个向量为16字节。每次从源位图读取3个向量,就是读取了48字节,即16个RGB像素。最后将灰度向量做“3-元素组的交织运算”,结果是3个向量。将那3个向量存储到目标位图,就是写入了48字节,即16个RGB像素。

对于3-元素组的交织,可以使用 shuffle 类别的指令来实现。例如对于X86架构的 128位向量,可以使用 SSSE3 的 _mm_shuffle_epi8 指令,它对应 NET 中的 Ssse3.Shuffle 方法。源代码如下。

static readonly Vector128<byte> YGroup3Zip_Shuffle_Byte_X_Part0 = Vector128.Create((sbyte)0, -1, -1, 1, -1, -1, 2, -1, -1, 3
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值