Why must "stride" in the System.Drawing.Bitmap constructor be a multiple of 4?
Asked Answered
F

7

29

I am writing an application that requires me to take a proprietary bitmap format (an MVTec Halcon HImage) and convert it into a System.Drawing.Bitmap in C#.

The only proprietary functions given to me to help me do this involve me writing to file, except for the use of a "get pointer" function.

This function is great, it gives me a pointer to the pixel data, the width, the height, and the type of the image.

My issue is that when I create my System.Drawing.Bitmap using the constructor:

new System.Drawing.Bitmap(width, height, stride, format, scan)

I need to specify a "stride" that is a multiple of 4. This may be a problem as I am unsure what size bitmap my function will be hit with. Supposing I end up with a bitmap that is 111x111 pixels, I have no way to run this function other than adding a bogus column to my image or subtracting 3 columns.

Is there a way I can sneak around this limitation?

Fermentative answered 2/2, 2010 at 16:56 Comment(0)
T
72

This goes back to early CPU designs. The fastest way to crunch through the bits of the bitmap is by reading them 32-bits at a time, starting at the start of a scan line. That works best when the first byte of the scan line is aligned on a 32-bit address boundary. In other words, an address that's a multiple of 4. On early CPUs, having that first byte mis-aligned would cost extra CPU cycles to read two 32-bit words from RAM and shuffle the bytes to create the 32-bit value. Ensuring each scan line starts at an aligned address (automatic if the stride is a multiple of 4) avoids that.

This isn't a real concern anymore on modern CPUs, now alignment to the cache line boundary is much more important. Nevertheless, the multiple of 4 requirement for stride stuck around for appcompat reasons.

Btw, you can easily calculate the stride from the format and width with this:

        int bitsPerPixel = ((int)format & 0xff00) >> 8;
        int bytesPerPixel = (bitsPerPixel + 7) / 8;
        int stride = 4 * ((width * bytesPerPixel + 3) / 4);
Teakettle answered 2/2, 2010 at 18:9 Comment(6)
This is the correct answer; plus one all day. Here's more on calculating stride: #1984281.Frances
So, I should most likely try to change my image format. Right now I'm using something that stores each pixel as a single byte. Having said that, I suppose I can't use that Bitmap constructor as there is no image type that just takes a single byte and doesn't rely on a wonky color map.Fermentative
That's correct, 8bpp requires a palette. GDI+ does not support them well, it will give you no end of hassle. You can sorta get it going by loading an existing 8bpp image to get started. You can steal its palette entries or LockBits it directly if it has the right size. Or synthesize one in a MemoryStream.Teakettle
Thank you all for your input. I ended up making my own palette, and for now I'm just going to force my image width to be a multiple of 4.Fermentative
@HansPassant hi! I'm interested in understanding why the first byte of the scanline has to be aligned on a 32-bit address boundary and I'm not sure I could get that well from the answer. could you please provide more detailed resources on that? thanks!Itu
The calculation for the stride is actually incorrect for formats below 8bpp; the bitsPerPixel needs to be multiplied by the width immediately, before the (x+7)/8 is done to it. "Bytes per pixel" isn't meaningful data if that can't be expressed as integer value, as is the case with 4-bit or 1-bit images.Piscina
P
7

A much easier way is to just make the image with the (width, height, pixelformat) constructor. Then it takes care of the stride itself.

Then, you can just use LockBits to copy your image data into it, line by line, without bothering with the Stride stuff yourself; you can literally just request that from the BitmapData object. For the actual copy operation, for each scanline, you just increase the target pointer by the stride, and the source pointer by your line data width.

Here's an example where I got the image data in a byte array. If that's completely compact data, your input stride is normally just the image width multiplied by the amount of bytes per pixel. If it's 8-bit paletted data, it's simply exactly the width.

If the image data was extracted from an image object, you should've stored the original stride from that extraction process in exactly the same way, by getting it out of the BitmapData object.

/// <summary>
/// Creates a bitmap based on data, width, height, stride and pixel format.
/// </summary>
/// <param name="sourceData">Byte array of raw source data</param>
/// <param name="width">Width of the image</param>
/// <param name="height">Height of the image</param>
/// <param name="stride">Scanline length inside the data</param>
/// <param name="pixelFormat">Pixel format</param>
/// <param name="palette">Color palette</param>
/// <param name="defaultColor">Default color to fill in on the palette if the given colors don't fully fill it.</param>
/// <returns>The new image</returns>
public static Bitmap BuildImage(Byte[] sourceData, Int32 width, Int32 height, Int32 stride, PixelFormat pixelFormat, Color[] palette, Color? defaultColor)
{
    Bitmap newImage = new Bitmap(width, height, pixelFormat);
    BitmapData targetData = newImage.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, newImage.PixelFormat);
    Int32 newDataWidth = ((Image.GetPixelFormatSize(pixelFormat) * width) + 7) / 8;
    // Compensate for possible negative stride on BMP format.
    Boolean isFlipped = stride < 0;
    stride = Math.Abs(stride);
    // Cache these to avoid unnecessary getter calls.
    Int32 targetStride = targetData.Stride;
    Int64 scan0 = targetData.Scan0.ToInt64();
    for (Int32 y = 0; y < height; y++)
        Marshal.Copy(sourceData, y * stride, new IntPtr(scan0 + y * targetStride), newDataWidth);
    newImage.UnlockBits(targetData);
    // Fix negative stride on BMP format.
    if (isFlipped)
        newImage.RotateFlip(RotateFlipType.Rotate180FlipX);
    // For indexed images, set the palette.
    if ((pixelFormat & PixelFormat.Indexed) != 0 && palette != null)
    {
        ColorPalette pal = newImage.Palette;
        for (Int32 i = 0; i < pal.Entries.Length; i++)
        {
            if (i < palette.Length)
                pal.Entries[i] = palette[i];
            else if (defaultColor.HasValue)
                pal.Entries[i] = defaultColor.Value;
            else
                break;
        }
        newImage.Palette = pal;
    }
    return newImage;
}
Piscina answered 14/5, 2017 at 18:44 Comment(0)
S
4

As has been stated before by Jake you calculate the stride by finding the bytes per pixel (2 for 16 bit, 4 for 32 bit) and then multiplying it by the width. So if you have a width of 111 and a 32 bit image you would have 444 which is a multiple of 4.

However, let's say for a minute that you have a 24 bit image. 24 bit is equal to 3 bytes, so with a 111 pixel width you would have 333 as your stride. This is, obviously, not a multiple of 4. So you would want to round up to 336 (the next highest multiple of 4). Even though you have a bit of extra, this unused space is not significant enough to really make much of a difference in most applications.

Unfortunately, there is no way around this restriction (unless you always use 32 bit or 64 bit imagines, which are always multiples of 4.

Savage answered 2/2, 2010 at 17:15 Comment(0)
J
2

Remember stride is different from width. You can have an image that has 111 (8-bit) pixels per line, but each line is stored in memory 112 bytes.

This is done to make efficient use of memory and as @Ian said, it's storing the data in int32.

Justifier answered 2/2, 2010 at 17:17 Comment(0)
I
1

Because it's using int32 to store each pixel.

Sizeof(int32) = 4

But don't worry, when the image is saved from memory to file it will use the most efficient memory usage possible. Internally it uses 24 bits per pixel (8 bits red, 8 green and 8 blue) and leaves the last 8 bits redundant.

Intisar answered 2/2, 2010 at 17:5 Comment(0)
A
1

Correct code:

public static void GetStride(int width, PixelFormat format, ref int stride, ref int bytesPerPixel)
{
    //int bitsPerPixel = ((int)format & 0xff00) >> 8;
    int bitsPerPixel = System.Drawing.Image.GetPixelFormatSize(format);
    bytesPerPixel = (bitsPerPixel + 7) / 8;
    stride = 4 * ((width * bytesPerPixel + 3) / 4);
}
Amman answered 14/7, 2011 at 17:7 Comment(1)
Not for formats below 8bpp though.Piscina
D
0

The accepted answer has a comment that the formula breaks down when bits per pixel is below 8. This calculation, which is also arguably slightly simpler, should work for any pixel depth:

int bpp = 6;                       // Could be derived automatically
int rowLength = imageWidth * bpp;
int stride = (rowLength + 32 - ((rowLength-1) % 32)) >> 3;
Durative answered 15/2 at 11:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.