Search code examples
c#memorybitmapmarshallingaccess-violation

Extract area from bitmap results in AccessViolationException when using Marshal.Copy


I am trying to extract a specific area from a bitmap for further processing. In rare cases an error occurs when Marshal.Copy is called. This can be reproduced with the following example code:

Bitmap bitmap = new Bitmap(1741, 2141, PixelFormat.Format1bppIndexed);

int zoneWidth = 50;
int zoneHeight = 50;
int x = 168;
int y = bitmap.Height - zoneHeight;
Rectangle zone = new Rectangle(x, y, zoneWidth, zoneHeight);

BitmapData bitmapData = bitmap.LockBits(zone, ImageLockMode.ReadOnly, bitmap.PixelFormat);
int byteCount = Math.Abs(bitmapData.Stride) * bitmapData.Height;
byte[] pixels = new byte[byteCount];

Marshal.Copy(bitmapData.Scan0, pixels, 0, byteCount);

// some further processing

bitmap.UnlockBits(bitmapData);

In other posts I have read that Stride can be negative. That is not the case here.

Why does the error occur and how can I prevent it?


Edit 1:

I have implemented the second suggestion of JonasH. But that also fails with the AccessViolationException. Probably I did not do that correctly.

Bitmap bitmap = new Bitmap(1741, 2141, PixelFormat.Format1bppIndexed);

int zoneWidth = 50;
int zoneHeight = 50;
int zoneX = 168;
int zoneY = bitmap.Height - zoneHeight;
Rectangle zone = new Rectangle(zoneX, zoneY, zoneWidth, zoneHeight);

BitmapData bitmapData = bitmap.LockBits(zone, ImageLockMode.ReadOnly, bitmap.PixelFormat);

int rowSize = Math.Abs(bitmapData.Stride);
byte[] pixels = new byte[bitmapData.Height * rowSize];
IntPtr iptr = bitmapData.Scan0;

for (int y = 0; y < bitmapData.Height; y++)
{
    Marshal.Copy(IntPtr.Add(iptr, y * rowSize),
        pixels,
        y * rowSize,
        rowSize);
}

bitmap.UnlockBits(bitmapData);

Solution

  • This is probably because you are only locking part of the bitmap. Replace the zone with new Rectangle(0, 0, bitmap.Width , bitmap.Height); and I would expect your problem to disappear.

    The alternative would be to restrict the copy-operation to the locked part of the bitmap, but that would require copying row by row and not the entire bitmap at once.

    Copying row by row needs careful usage of offsets, you need to keep track of both the horizontal and vertical offsets of both the source and target data. I think something like this should work:

    
    for (int y = 0; y < zoneHeight ; y++)
    {
        Marshal.Copy(
            IntPtr.Add(iptr,(y + zoneY ) * bitmapData.Stride + zoneX)
            pixels,
            y * zoneWidth,
            zoneWidth );
    }