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?

13
  • stackoverflow.com/questions/31045358/… Commented Nov 26, 2016 at 7: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...? Commented Nov 26, 2016 at 8:05
  • Some sample data would go a long way to getting a proper answer. Commented Nov 26, 2016 at 8: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. Commented Nov 26, 2016 at 8:10
  • 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; Commented Nov 26, 2016 at 8:26

3 Answers 3

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;
}
Sign up to request clarification or add additional context in comments.

1 Comment

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

1 Comment

It's even faster when we skip all unsafe fixed thing. See my updated question. We can get managed array of uints and process them in managed code using some bitwise logic.
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.

4 Comments

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 I mostly worked from the sentence: I wish something like this worked: var pixels = data as Bgra32Pixel[];
@Abion47 and his first sentence: TL; DR: I have byte[]. I want Bgra32Pixel[]
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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.