如何在C#中将byte []转换为struct FAST数组?

时间:2016-11-26 07:56:01

标签: c#

TL; DR:我有byte[]。我想要Bgra32Pixel[]。不想复制。如果需要复制,我想要最快的优化复制,不要复制单个字节。它甚至可能吗?

完整说明:

这是结构:

/// <summary>
/// Represents a PixelFormats.Bgra32 format pixel
/// </summary>
[StructLayout(LayoutKind.Explicit)]
public struct Bgra32Pixel {

    [FieldOffset(0)]
    public readonly int Value;

    [FieldOffset(0)]
    public byte B;

    [FieldOffset(1)]
    public byte G;

    [FieldOffset(2)]
    public byte R;

    [FieldOffset(3)]
    public byte A;

}

我有一个字节数组,我们称之为data。我希望以Bgra32Pixel[]的形式访问它。 内存中的字节相同。是否有必要为其复制字节?

我希望这样的事情有效:

var pixels = data as Bgra32Pixel[];

但事实并非如此。最快的方法是什么?

我的猜测是使用索引器自定义类型直接从原始byte []引用返回Bgra32Pixel。但它不会很快。不需要复制,但每次访问实际上将创建一个4字节的新结构。不,似乎不必要的慢。必须有一种方法来欺骗C#,认为byte []以某种方式Bgra32Pixel []。

所以这是我在阅读完所有答案后找到的解决方案:

TL; DR:不需要结构。

转换为struct需要不安全的上下文和固定的语句。这对性能没有好处。这是从位图中删除背景的代码,它假设左上角的像素具有背景颜色。此代码在每个像素上调用特殊的“颜色到alpha”voodoo:

/// <summary>
/// Extensions for bitmap manipulations.
/// </summary>
static class BitmapSourceExtensions {

    /// <summary>
    /// Removes the background from the bitmap assuming the first pixel is background color.
    /// </summary>
    /// <param name="source">Opaque bitmap.</param>
    /// <returns>Bitmap with background removed.</returns>
    public static BitmapSource RemoveBackground(this BitmapSource source) {
        if (source.Format != PixelFormats.Bgr32) throw new NotImplementedException("Pixel format not implemented.");
        var target = new WriteableBitmap(source.PixelWidth, source.PixelHeight, source.DpiX, source.DpiY, PixelFormats.Bgra32, null);
        var pixelSize = source.Format.BitsPerPixel / 8;
        var pixelCount = source.PixelWidth * source.PixelHeight;
        var pixels = new uint[pixelCount];
        var stride = source.PixelWidth * pixelSize;
        source.CopyPixels(pixels, stride, 0);
        var background = new LABColor(pixels[0]);
        for (int i = 0; i < pixelCount; i++) pixels[i] &= background.ColorToAlpha(pixels[i]);
        var bounds = new Int32Rect(0, 0, source.PixelWidth, source.PixelHeight);
        target.WritePixels(bounds, pixels, stride, 0);
        return target;
    }

}

如果你很好奇,那么使用的伏都教课程是什么:

/// <summary>
/// CIE LAB color space structure with BGRA pixel support.
/// </summary>
public struct LABColor {

    /// <summary>
    /// Lightness (0..100).
    /// </summary>
    public readonly double L;

    /// <summary>
    /// A component (0..100)
    /// </summary>
    public readonly double A;

    /// <summary>
    /// B component (0..100)
    /// </summary>
    public readonly double B;

    /// <summary>
    /// Creates CIE LAB color from BGRA pixel.
    /// </summary>
    /// <param name="bgra">Pixel.</param>
    public LABColor(uint bgra) {
        const double t = 1d / 3d;
        double r = ((bgra & 0x00ff0000u) >> 16) / 255d;
        double g = ((bgra & 0x0000ff00u) >> 8) / 255d;
        double b = (bgra & 0x000000ffu) / 255d;
        r = (r > 0.04045 ? Math.Pow((r + 0.055) / 1.055, 2.4) : r / 12.92) * 100d;
        g = (g > 0.04045 ? Math.Pow((g + 0.055) / 1.055, 2.4) : g / 12.92) * 100d;
        b = (b > 0.04045 ? Math.Pow((b + 0.055) / 1.055, 2.4) : b / 12.92) * 100d;
        double x = (r * 0.4124 + g * 0.3576 + b * 0.1805) / 95.047;
        double y = (r * 0.2126 + g * 0.7152 + b * 0.0722) / 100.000;
        double z = (r * 0.0193 + g * 0.1192 + b * 0.9505) / 108.883;
        x = x > 0.0088564516790356311 ? Math.Pow(x, t) : (903.2962962962963 * x + 16d) / 116d;
        y = y > 0.0088564516790356311 ? Math.Pow(y, t) : (903.2962962962963 * y + 16d) / 116d;
        z = z > 0.0088564516790356311 ? Math.Pow(z, t) : (903.2962962962963 * z + 16d) / 116d;
        L = Math.Max(0d, 116d * y - 16d);
        A = 500d * (x - y);
        B = 200d * (y - z);
    }


    /// <summary>
    /// Calculates color space distance between 2 CIE LAB colors.
    /// </summary>
    /// <param name="c">CIE LAB color.</param>
    /// <returns>A color space distance between 2 colors from 0 (same colors) to 100 (black and white)</returns>
    public double Distance(LABColor c) {
        double dl = L - c.L;
        double da = A - c.A;
        double db = B - c.B;
        return Math.Sqrt(dl * dl + da * da + db * db);
    }

    /// <summary>
    /// Calculates bit mask for alpha calculated from difference between this color and another BGRA color.
    /// </summary>
    /// <param name="bgra">Pixel.</param>
    /// <returns>Bit mask for alpha in BGRA pixel format.</returns>
    public uint ColorToAlpha(uint bgra) => 0xffffffu | ((uint)(Distance(new LABColor(bgra)) * 2.55d) << 24);

}

我正在回馈社区。我在StackOverflow和Github上找到了所有必要的数学。 我猜GIMP正在使用非常类似的“颜色到alpha”效果。

问题仍然存在:是否有更快的方法可以做到这一点?

3 个答案:

答案 0 :(得分:2)

不需要将字节数组转换为Bgra32Pixel个对象,这样做只会损害您的性能。要从WriteableBitmap读取像素数据,您可以执行以下操作:

unsafe public static BitmapSource GetBgra32(this BitmapSource bmp) 
{
    if (bmp.Format != PixelFormats.Bgr32) 
        throw new NotImplementedException("Pixel format not implemented.");

    var source = new WriteableBitmap(bmp);
    var target = new WriteableBitmap(bmp.PixelWidth, bmp.PixelHeight, bmp.DpiX, bmp.DpiY, PixelFormats.Bgra32, null);

    source.Lock();
    target.Lock();

    var srcPtr = (byte*) source.BackBuffer;
    var trgPtr = (byte*) source.BackBuffer;

    int sIdx,sCol,tIdx,tCol;
    for (int y = 0; y < bmp.PixelHeight; y++)
    {
        sCol = y * source.BackBufferStride;
        tCol = y * target.BackBufferStride;

        for (int x = 0; x < bmp.PixelWidth; x++)
        {
            sIdx = sCol + (x * 3); // Bpp = 3
            tIdx = tCol + (x * 4); // Bpp = 4

            byte b = srcPtr[sIdx];
            byte g = srcPtr[sIdx + 1];
            byte r = srcPtr[sIdx + 2];

            // Do some processing

            trgPtr[tIdx] = bVal;
            trgPtr[tIdx + 1] = gVal;
            trgPtr[tIdx + 2] = rVal;
            trgPtr[tIdx + 3] = aVal;
        }
    }

    source.Unlock();
    target.Unlock();

    return target;
}

答案 1 :(得分:1)

以下代码可用于将byte数组转换为Bgra32Pixel数组。

static T[] ConvertBytesWithGCHandle<T>(byte[] data) where T : struct
{
    int size = Marshal.SizeOf(typeof(T));

    if (data.Length % size != 0) throw new ArgumentOutOfRangeException("data", "Data length must be divisable by " + size + " (struct size).");

    GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned);
    IntPtr ptr = handle.AddrOfPinnedObject();

    T[] returnData = new T[data.Length / size];
    for (int i = 0; i < returnData.Length; i++)
        returnData[i] = (T)Marshal.PtrToStructure(ptr + i * size, typeof(T));

    handle.Free();
    return returnData;
}

static T[] ConvertBytesWithMarshal<T>(byte[] data) where T : struct
{
    int size = Marshal.SizeOf(typeof(T));

    if (data.Length % size != 0) throw new ArgumentOutOfRangeException("data", "Data length must be divisable by " + size + " (struct size).");

    T[] returnData = new T[data.Length / size];
    for (int i = 0; i < returnData.Length; i++)
    {
        IntPtr ptr = Marshal.AllocHGlobal(size);
        Marshal.Copy(data, i * size, ptr, size);
        returnData[i] = (T)Marshal.PtrToStructure(ptr, typeof(T));
        Marshal.FreeHGlobal(ptr);
    }
    return returnData;
}

对于速度测试,我还做了以下方法:

static Bgra32Pixel[] CopyBytesWithPlain(byte[] data)
{
    if (data.Length % 4 != 0) throw new ArgumentOutOfRangeException("data", "Data length must be divisable by 4 (struct size).");

    Bgra32Pixel[] returnData = new Bgra32Pixel[data.Length / 4];

    for (int i = 0; i < returnData.Length; i++)
        returnData[i] = new Bgra32Pixel()
        {
            B = data[i * 4 + 0],
            G = data[i * 4 + 1],
            R = data[i * 4 + 2],
            A = data[i * 4 + 3]
        };
    return returnData;
}

static Bgra32Pixel[] CopyBytesWithLinq(byte[] data)
{
    if (data.Length % 4 != 0) throw new ArgumentOutOfRangeException("data", "Data length must be divisable by 4 (struct size).");

    return data
        .Select((b, i) => new { Byte = b, Index = i })
        .GroupBy(g => g.Index / 4)
        .Select(g => g.Select(b => b.Byte).ToArray())
        .Select(a => new Bgra32Pixel()
        {
            B = a[0],
            G = a[1],
            R = a[2],
            A = a[3]
        })
        .ToArray();
}

速度测试的结果如下:

CopyBytesWithPlain:    00:00:00.1701410 
CopyBytesWithGCHandle: 00:00:02.8298880 
CopyBytesWithMarshal:  00:00:05.5448466
CopyBytesWithLinq:     00:00:33.5987996

代码可以按如下方式使用:

var bytes = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7 };
var pixels = ConvertBytesWithMarshal<Bgra32Pixel>(bytes);
foreach (var pixel in pixels)
    Console.WriteLine(pixel.Value);

或者

var bytes = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7 };
var pixels = ConvertBytesWithGCHandle<Bgra32Pixel>(bytes);
foreach (var pixel in pixels)
    Console.WriteLine(pixel.Value);

代码基于https://stackoverflow.com/a/31047345/637425Taha Paksuhttps://stackoverflow.com/a/2887/637425指出,正如 gabriel

答案 2 :(得分:1)

您不需要将byte数组转换为Bgra32Pixel数组,您只需访问字节数组,因为它是Bgra32Pixel数组。

以下代码创建一个缓冲区,用于保存32个像素并将其颜色设置为半透明红色:

const int numberOfPixels = 32;
var buffer = new byte[numberOfPixels * sizeof(Bgra32Pixel)];
fixed(void* p =  buffer)
{
    var pixels = (Bgra32Pixel*)p;
    for (int i= 0; i < numberOfPixels; i++)
    {
        pixels[i].A = 128;
        pixels[i].R = 255;
    }
}

但是,如果您将像素视为整个uint

,则会更快

以下代码执行相同操作,但速度更快:

const int numberOfPixels = 32;
var buffer = new byte[numberOfPixels * sizeof(uint)];
fixed(void* p =  buffer)
{
    var pixels = (uint*)p;
    for (int i= 0; i < numberOfPixels; i++)
    {
        pixels[i] = 0x80FF0000; // A: 0x80, R: 0xFF, G: 0x00, B: 0x00
    }
}