Fredou
Fredou

Reputation: 20120

Way to make this unsafe struct with fixed array dynamic? (or an alternative?)

I'm currently having this;

    private const int PixelSizeBGR = 3;

    [StructLayout(LayoutKind.Explicit)]
    private unsafe struct BGR5
    {
        [FieldOffset(0)]
        private fixed byte bgr[PixelSizeBGR * 5];

        public BGR5(byte b, byte g, byte r)
        {
            fixed (byte* v = bgr) 
            {
                int num = 0;
                do
                {
                    v[num++] = b; v[num++] = g; v[num++] = r;
                } while (num < (PixelSizeBGR * 5));
            }
        }
    }

    [StructLayout(LayoutKind.Explicit)]
    private unsafe struct BGR3
    {
        [FieldOffset(0)]
        private fixed byte bgr[PixelSizeBGR * 3];

        public BGR3(byte b, byte g, byte r)
        {
            fixed (byte* v = bgr)
            {
                int num = 0;
                do
                {
                    v[num++] = b; v[num++] = g; v[num++] = r;
                } while (num < (PixelSizeBGR * 3));
            }
        }
    }

you can see the pattern I think.

Is there a way to make it dynamic since i might find out that i need more of these?

or is there any alternative?

practical example;

BEFORE, 24000 pixel by 24000 pixel bitmap, 2151 milliseconds

            byte* row = (byte*)bmd.Scan0;

            /*** stuff ***/

            Offset1 = ((CurrentPos / GridX) * FullRow) + ((CurrentPos % GridX) * FullSquare);
            for (k = PixelSize; k <= w; k += PixelSize)
            {
                Offset1 += PixelSize;
                for (l = Stride; l <= h; l += Stride)
                {
                    row[l + Offset1] = 0; //b
                    row[l + Offset1 + 1] = 255; //g
                    row[l + Offset1 + 2] = 255; //r
                }
            }
            /*** more stuff ***/

AFTER, 24000 pixel by 24000 pixel bitmap, 944 milliseconds

            byte* row = (byte*)bmd.Scan0;

            /*** stuff ***/

            np5.Set(0, 255, 255);
            Offset1 = ((CurrentPos / GridX) * FullRow) + ((CurrentPos % GridX) * FullSquare) + PixelSizeBGR;
            h = Stride;
            do
            {
                *((BGR5*)(row + h + Offset1)) = np5;
                h += Stride;
            } while (h < FullRow);

            /*** more stuff ***/

AFTER is more than 50% faster

Upvotes: 1

Views: 843

Answers (2)

nicholas
nicholas

Reputation: 3047

Without going into the question of whether this should be done or if there are better ways of doing this, I will attempt to answer the OP's desire to create runtime sizeable structures that can be used for variable length memory block copies. In short, performing high performance byte-level manipulations of data is more suited for C++ than C#, but it appears it is still technically possible.

To accomplish this, we can use marshalling to dynamically create unmanaged memory of variable size to hold our temporary BGRn data and then use P/Invoke to perform the block memory copy.

public unsafe class BGRn
{
    IntPtr bgrPtr;
    int totalSize;
    const int pixelSizeBGR = 3;

    public BGRn(byte b, byte g, byte r, int size)
    {
        totalSize = pixelSizeBGR * size;
        bgrPtr = Marshal.AllocHGlobal(totalSize);

        byte* v = (byte*)bgrPtr;

        int num = 0;

        do
        {
            v[num++] = b;
            v[num++] = g;
            v[num++] = r;
        } while (num < (totalSize));
    } 

    public void CopyTo(IntPtr buffer)
    {
        CopyMemory(buffer, bgrPtr, (uint) totalSize);
    }

    [DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)]
    private static extern void CopyMemory(IntPtr dest, IntPtr src, uint count);

}

You can then leave the other code as is except replace:

BGR5 np5 = new BGR5(0, 255, 255);
// ...
*((BGR5*)(row + h + Offset1)) = np5;

with:

BGRn np5 = new BGRn(0, 255, 255, 5);
// ...
np5.CopyTo((IntPtr)(row + h + Offset1));

Of course, I have left out the Marshal.FreeHGlobal that should go along with this class, but that is up to the user to decide how and when to free the memory (probably by implementing IDisposable and a using statement).

I have not actually tested how this code performs or if it even works, but it does compile.

Upvotes: 1

RoadBump
RoadBump

Reputation: 751

Using an absract base class can ease the work with bitmaps of any width, albeit you will still need to write a separate class for each bitmap width. You don't need structs- you can write direct to bitmap's memory.

using System.Drawing.Imaging;

namespace TestProject2
{
public abstract class BGRBase
{
    //init to your bitmap's BitmapData, obtained by calling Bitmap.LockBits
    protected readonly BitmapData data;

    public abstract void SetRow(int rowIndex, byte b, byte g, byte r);
}

public class BGR3 : BGRBase
{
    //use constructor to ensure that the bitmap's width is compatible

    public unsafe override void SetRow(int rowIndex, byte b, byte g, byte r)
    {
        byte* row = (byte*)data.Scan0 + rowIndex * data.Stride;
        row[0] = row[3] = row[6] = b;
        //etc
    }
}

public class BGR5 : BGRBase
{
    public override unsafe void SetRow(int rowIndex, byte b, byte g, byte r)
    {
        //code as adove
    }
}
}

Or, use a delegate to encapsulate an appropriate method and call it in the loop

public static void Set5(byte* p, byte b, byte g, byte r)

public static void Set3(byte* p, byte b, byte g, byte r)

//usage
public void Draw(Rectangle rect, byte b, byte g, byte r)
{
    Action<byte*, byte, byte, byte> setRow = null;
    switch(rect.Width)
    {
        case 3: setRow = Set3; break;
        case 5: setRow = Set5; break;
        //etc
    }
    byte* p=(byte*)bmd.Scan0 + bmd.Stride * rect.Y + 3 * rect.X;
    while(p < endAddress)
    {
        setRow(p, b, g, r);
        p+=bmd.Stride;  
    }
}

Upvotes: 0

Related Questions