Search code examples
c#.netimage-processingbitmapstride

Slanted bitmap, stride calculation for RGB565 C#


Some of my resulting images are slanted, some are not.

Expected Result: (529x22)

enter image description here

Actual Result: (529x22)

Don't mind the different image sizes, these are screenshots. They are both 529x22.

enter image description here

The code I am using, I just got this from an answer on a question here at SO.

// some other method
byte[] pixels = new byte[size - 16];
Array.Copy(this.data, offset, pixels, 0, pixels.Length);
this.ByteToImage(w, h, pixels);

// builds the pixels to a image
private Bitmap ByteToImage(int w, int h, byte[] pixels)
{
    var bmp = new Bitmap(w, h, PixelFormat.Format16bppRgb565);

    var BoundsRect = new Rectangle(0, 0, bmp.Width, bmp.Height);
    BitmapData bmpData = bmp.LockBits(BoundsRect,
                                    ImageLockMode.WriteOnly,
                                    bmp.PixelFormat);

    // bytes => not using this because it gives error
    // eg. pixel.Length = 16032, bytes = 16064
    int bytes = bmpData.Stride * bmp.Height;

    Marshal.Copy(pixels, 0, bmpData.Scan0, pixels.Length);
    bmp.UnlockBits(bmpData);

    return bmp;
}

I'm confused because some works ok, not slanted. But others are slanted. What did I miss?

Update

As stated in the comments and answers, the problem is how I'm calculating stride. I'm still confused on how to do it but I tried this:

public static void RemovePadding(this Bitmap bitmap)
{
    int bytesPerPixel = Image.GetPixelFormatSize(bitmap.PixelFormat) / 8;

    BitmapData bitmapData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, bitmap.PixelFormat);
    var pixels = new byte[bitmapData.Width * bitmapData.Height * bytesPerPixel];

    for (int row = 0; row < bitmapData.Height; row++)
    {
        var dataBeginPointer = IntPtr.Add(bitmapData.Scan0, row * bitmapData.Stride);
        Marshal.Copy(dataBeginPointer, pixels, row * bitmapData.Width * bytesPerPixel, bitmapData.Width * bytesPerPixel);
    }

    Marshal.Copy(pixels, 0, bitmapData.Scan0, pixels.Length);
    bitmap.UnlockBits(bitmapData);
}

But the result is (more slanted):

enter image description here


Solution

  • This seems to work here:

    private Bitmap ByteToImage(int w, int h, byte[] pixels)
    {
        var bmp = new Bitmap(w, h, PixelFormat.Format16bppRgb565);
        byte bpp = 2;
        var BoundsRect = new Rectangle(0, 0, bmp.Width, bmp.Height);
        BitmapData bmpData = bmp.LockBits(BoundsRect,
                                        ImageLockMode.WriteOnly,
                                        bmp.PixelFormat);
        // copy line by line:
        for (int y = 0; y < h; y++ )
            Marshal.Copy(pixels, y * w * bpp, bmpData.Scan0 + bmpData.Stride * y, w * bpp);
        bmp.UnlockBits(bmpData);
    
        return bmp;
    }
    

    I use a loop to place each row of data at the right spot. The data do not include the padding, but the target address must do so.

    Therefore we need to multiply the data access by the actual width * bytePerPixel but the target adress by the Stride, i.e. the length of the scanline, padded to the next multiple of four bytes. For width=300 it is stride=300, for width=301 it is stride=304..

    Moving all pixel data in one step can only work when there is no padding, i.e. when the width is a multiple of 4.