0

TL; DR: I have byte[]. I want Bgra32Pixel[]. Don't want to copy. If a copy is necessary, I want fastest possible optimized copy, no copying of single bytes. Is it even possible?

Full description:

Here's the struct:

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

}

I have a byte array, lets call it data. I want to access it as Bgra32Pixel[]. The same bytes in memory. Is it necessary to copy bytes for it?

I wish something like this worked:

var pixels = data as Bgra32Pixel[];

But it doesn't. What is the fastest way to do it?

My guess is to make custom type with indexer returning Bgra32Pixel directly from original byte[] reference. But it wouldn't be very fast. No copying is required for that, but each access would actually create a new struct from 4 bytes. No, seems unnecessary slow. There must be a way to trick C# into thinking byte[] is somehow Bgra32Pixel[].

So here's my solution I found after reading all the answers:

TL;DR: No need for struct.

Conversion to struct would require unsafe context and fixed statement. This is no good for performance. Here's the code for removing background from a bitmap, which assumes the pixel in the left top corner has the background color. This code calls special "color to alpha" voodoo on each pixel:

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

}

If you are very curious, what is the voodoo class used, here:

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

}

I'm giving back to the community. I found all necessary math on StackOverflow and Github. I guess GIMP is using something very similar for "color to alpha" effect.

The question still remains open: is there a faster way to do that?

Harry
  • 4,524
  • 4
  • 42
  • 81
  • http://stackoverflow.com/questions/31045358/how-do-i-copy-bytes-into-a-struct-variable-in-c – Taha Paksu Nov 26 '16 at 07:59
  • What is the format of the byte array? Is it a Bgra32Pixel that has been serialized? Or perhaps an array of bytes with values that happen to mesh with the B/G/R/A fields in a particular order? Or is the byte array a pointer to an address in memory that contains Bgra32Pixel data? Or...? – Abion47 Nov 26 '16 at 08:05
  • Some sample data would go a long way to getting a proper answer. – Abion47 Nov 26 '16 at 08:07
  • I know how to copy bytes into struct, but I ask how to convert array of bytes into array of structs. Best without copying. And yes, I want to do this unsafe way, since my byte array is in strictly defined format, no surprises. – Harry Nov 26 '16 at 08:10
  • @Abion47 : Here you are. I want the method to add transparency to opaque `Bgr32` bitmap depending on its background color. I need to sample some pixels from image "border", determine background color and then set alpha component of each pixel based on similarity to this color. There will be lots of images, so I want the fastest possible pixel access, yet I want the code to remain readable. – Harry Nov 26 '16 at 08:18
  • 1
    I don't understand your question, you already have a Bgra32Pixel[] like variable, It's p1, p1 is your Bgra32Pixel array. You can do things like p1[0].B = 45; – Jesús López Nov 26 '16 at 08:26
  • Thanks, I missed the point I've already done here ;) So no copying is done (except `CopyPixels` / `WritePixels`, but I think it's necessary minimum), pixels are accessed in fastest possible way. It's been a long time since I last used `fixed`, I forgot how to use it. – Harry Nov 26 '16 at 08:35
  • how about something like this http://stackoverflow.com/a/2887/2330575 ? – Gabriel Sadaka Nov 26 '16 at 08:37
  • @JesúsLópez : p1[0].B = 45 doesn't work, it doesn't change the buffer. Assignment is done, but I see no effect on the bitmap. I tried to set all pixels red, but all I see is original bitmap unchanged. – Harry Nov 26 '16 at 08:42
  • Yes, it does change the buffer. See the buffer array on the debugger and you will see it. – Jesús López Nov 26 '16 at 08:45
  • @JesúsLópez : YES! Sorry, I had a silly bug. It works. – Harry Nov 26 '16 at 08:52
  • @Harry. Have you seen my answer, I propose you to use uints instead of Bgra32Pixel's, it's even faster. – Jesús López Nov 26 '16 at 09:32
  • @JesúsLópez : Well, but then I would have to use some bitwise ops to access channels. My (hidden) goal is to remove background from a bitmap. Something like GIMP's "Color to Alpha" effect. – Harry Nov 26 '16 at 19:00

3 Answers3

2

There is no need to convert the byte array to Bgra32Pixel objects, and doing so is only going to hurt your performance. To read pixel data from a WriteableBitmap, you can do the following:

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;
}
Abion47
  • 22,211
  • 4
  • 65
  • 88
  • Yep, it seems like the fastest solution. Since I have to copy one bitmap to another pixel format, one copy operation is unavoidable. So it's best to calculate alpha at the same step. – Harry Nov 26 '16 at 13:33
1

The below code can be used to convert an array of byte into an array of 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;
}

For speed tests I also made the following methods:

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();
}

The results of the speed test are as follows:

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

Code can be used as follows:

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);

Or

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);

Code is based on https://stackoverflow.com/a/31047345/637425 as pointed out by Taha Paksu and on https://stackoverflow.com/a/2887/637425 as pointed out by gabriel.

Community
  • 1
  • 1
SynerCoder
  • 12,493
  • 4
  • 47
  • 78
  • 1
    That's an elegant solution, but it does not address OP's core problem, which is accessing pixel data from a BitmapSource in a timely manner. – Abion47 Nov 26 '16 at 08:58
  • @Abion47 I mostly worked from the sentence: I wish something like this worked: `var pixels = data as Bgra32Pixel[];` – SynerCoder Nov 26 '16 at 09:44
  • @Abion47 and his first sentence: TL; DR: I have byte[]. I want Bgra32Pixel[] – SynerCoder Nov 26 '16 at 10:21
  • That would be an example of an XY problem. OP I'd asking for the solution to a particular problem, but that problem is OP's chosen way to solve the overall problem. So rather than try to help him with the specific problem that is itself an inadvisable solution, it's better to help him with the root problem. – Abion47 Nov 26 '16 at 14:20
1

You don't need to convert the byte array to a Bgra32Pixel array, all you need is to access the byte array as it was a Bgra32Pixel array.

The following code creates a buffer for holding 32 pixels and set their color to semitransparent red:

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

But it would be even faster if you manage your pixels as whole uint's

The following code does the same, but faster:

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
    }
}
Jesús López
  • 8,338
  • 7
  • 40
  • 66
  • It's even faster when we skip all unsafe fixed thing. See my updated question. We can get managed array of `uint`s and process them in managed code using some bitwise logic. – Harry Nov 26 '16 at 22:31