【问题标题】:How to convert byte[] to array of struct FAST in C#?如何在 C# 中将 byte[] 转换为 struct FAST 数组?
【发布时间】:2016-11-26 07:56:00
【问题描述】:

TL;博士:我有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”巫毒:

/// <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;
    }

}

如果你很好奇,使用的voodoo类是什么,这里:

/// <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”的效果。

问题仍然悬而未决:有没有更快的方法来做到这一点?

【问题讨论】:

  • 字节数组的格式是什么?是不是已经序列化的Bgra32Pixel?或者可能是一个字节数组,其值恰好以特定顺序与 B/G/R/A 字段啮合?还是字节数组是指向内存中包含 Bgra32Pixel 数据的地址的指针?还是……?
  • 一些样本数据对于获得正确答案大有帮助。
  • 我知道如何将字节复制到结构中,但我问如何将字节数组转换为结构数组。最好不要抄袭。是的,我想做这种不安全的方式,因为我的字节数组是严格定义的格式,这不足为奇。
  • 我不明白你的问题,你已经有一个类似 Bgra32Pixel[] 的变量,它是 p1,p1 是你的 Bgra32Pixel 数组。你可以做 p1[0].B = 45;

标签: c#


【解决方案1】:

没有必要将字节数组转换为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;
}

【讨论】:

  • 是的,这似乎是最快的解决方案。由于我必须将一个位图复制到另一种像素格式,因此不可避免地需要进行一次复制操作。所以最好在同一步骤计算 alpha。
【解决方案2】:

您无需将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's 会更快

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

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
    }
}

【讨论】:

  • 跳过所有不安全的固定内容会更快。请参阅我更新的问题。我们可以获得uints 的托管数组,并使用一些按位逻辑在托管代码中处理它们。
【解决方案3】:

以下代码可用于将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);

代码基于Taha Paksu 指出的https://stackoverflow.com/a/31047345/637425https://stackoverflow.com/a/2887/637425 指出的 gabriel.

【讨论】:

  • 这是一个优雅的解决方案,但它没有解决 OP 的核心问题,即及时从 BitmapSource 访问像素数据。
  • @Abion47 我主要从以下句子开始工作:我希望这样的事情有效:var pixels = data as Bgra32Pixel[];
  • @Abion47 和他的第一句话:TL; DR:我有字节[]。我想要 Bgra32Pixel[]
  • 这将是一个 XY 问题的示例。 OP 我要求解决特定问题,但该问题是 OP 选择的解决整体问题的方法。因此,与其试图帮助他解决本身就是不明智的解决方案的特定问题,不如帮助他解决根本问题。
猜你喜欢
  • 1970-01-01
  • 2013-04-14
  • 2011-06-11
  • 2010-09-30
  • 1970-01-01
  • 2018-03-25
  • 1970-01-01
  • 2022-11-10
  • 2019-06-18
相关资源
最近更新 更多