Why does BitmapSource.Create throw an ArgumentException?

pamxy發表於2013-05-14

轉自:http://stackoverflow.com/questions/1983781/why-does-bitmapsource-create-throw-an-argumentexception



I'm trying to get an bitmap created from raw data to show in WPF, by using an Image and a BitmapSource:

Int32[] data = new Int32[RenderHeight * RenderWidth];

for (Int32 i = 0; i < RenderHeight; i++)
{
    for (Int32 j = 0; j < RenderWidth; j++)
    {
        Int32 index = j + (i * RenderHeight);

        if (i + j % 2 == 0)
            data[index] = 0xFF0000;
        else
            data[index] = 0x00FF00;
    }
}

BitmapSource source = BitmapSource.Create(RenderWidth, RenderHeight, 96.0, 96.0, PixelFormats.Bgr32, null, data, 0);

RenderImage.Source = source;

However the call to BitmapSource.Create throws an ArgumentException, saying "Value does not fall within the expected range". Is this not the way to do this? Am I not making that call properly?

share|improve this question
   
up vote16down voteaccepted

Your stride is incorrect. Stride is the number of bytes allocated for one scanline of the bitmap. Thus, use the following:

int stride = ((RenderWidth * 32 + 31) & ~31) / 8;

and replace the last parameter (currently 0) with stride as defined above.

Here is an explanation for the mysterious stride formula:

Fact: Scanlines must be aligned on 32-bit boundaries (reference).

The naive formula for the number of bytes per scanline would be:

(width * bpp) / 8

But this might not give us a bitmap aligned on a 32-bit boundary and (width * bpp) might not even have been divisible by 8.

So, what we do is we force our bitmap to have at least 32 bits in a row (we assume that width > 0):

width * bpp + 31

and then we say that we don't care about the low-order bits (bits 0--4) because we are trying to align on 32-bit boundaries:

(width * bpp + 31) & ~31

and then divide by 8 to get back to bytes:

((width * bpp + 31) & ~31) / 8

The padding can be computed by

int padding = stride - (((width * bpp) + 7) / 8)

The naive formula would be

stride - ((width * bpp) / 8)

But width * bpp might not align on a byte boundary and when it doesn't this formula would over count the padding by a byte. (Think of a 1 pixel wide bitmap using 1 bpp. The stride is 4 and the naive formula would say that the padding is 4 but in reality it is 3.) So we add a little bit to cover the case that width * bpp is not a byte boundary and then we get the correct formula given above.

share|improve this answer
 
Thank you, but how on earth did you come up with that expression? Why isn't it simply RenderWidth * 4? Isn't that the number of bytes for one line? – Mike Pateras Dec 31 '09 at 4:26
 
Sorry, I should have provided details. In your case you have bpp = 32 so yes the formula reduces toRenderWidth * 4. But there are odd cases (cheap LCDs use 18 bpp) and the fact that scanlines have to be aligned on 32-bit boundaries. I provided the general formula and an explanation of how to come up with it above. Hope it's elucidating. – Jason Dec 31 '09 at 5:09
 
Thank you. One more question. What does the tilde do on an integer like that? – Mike Pateras Dec 31 '09 at 5:23
 
@Mike Pateras: It is the bitwise not operator. That means that it flips the bits in the binary representation of the integer (so 0 becomes 1 and 1 becomes 0). I said that we want to ignore the lowest five bits ofwidth * bpp + 31 (and implicitly keep the rest). An easy way to do that is to make a number that has 0in those five bits and a 1 in the rest of the bits; this is called a bitmask. If we take the logical and width * bpp + 31 with this bitmask (~31) we have masked away the five low-order bits that we don't care about. Let me know if that isn't clear. – Jason Dec 31 '09 at 5:28
1  
I spent all day messing with this and your post helped me fix my code in 30 seconds. Thank you VERY much!!! and if anyone needs to know, ((width * 24 + 23) & ~23) / 8; works withPixelFormats.Rgb24;. – PiZzL3 Jan 10 '12 at 3:52

I'm trying to get an bitmap created from raw data to show in WPF, by using an Image and a BitmapSource:

Int32[] data = new Int32[RenderHeight * RenderWidth];

for (Int32 i = 0; i < RenderHeight; i++)
{
    for (Int32 j = 0; j < RenderWidth; j++)
    {
        Int32 index = j + (i * RenderHeight);

        if (i + j % 2 == 0)
            data[index] = 0xFF0000;
        else
            data[index] = 0x00FF00;
    }
}

BitmapSource source = BitmapSource.Create(RenderWidth, RenderHeight, 96.0, 96.0, PixelFormats.Bgr32, null, data, 0);

RenderImage.Source = source;

However the call to BitmapSource.Create throws an ArgumentException, saying "Value does not fall within the expected range". Is this not the way to do this? Am I not making that call properly?

share|improve this question
   
up vote16down voteaccepted

Your stride is incorrect. Stride is the number of bytes allocated for one scanline of the bitmap. Thus, use the following:

int stride = ((RenderWidth * 32 + 31) & ~31) / 8;

and replace the last parameter (currently 0) with stride as defined above.

Here is an explanation for the mysterious stride formula:

Fact: Scanlines must be aligned on 32-bit boundaries (reference).

The naive formula for the number of bytes per scanline would be:

(width * bpp) / 8

But this might not give us a bitmap aligned on a 32-bit boundary and (width * bpp) might not even have been divisible by 8.

So, what we do is we force our bitmap to have at least 32 bits in a row (we assume that width > 0):

width * bpp + 31

and then we say that we don't care about the low-order bits (bits 0--4) because we are trying to align on 32-bit boundaries:

(width * bpp + 31) & ~31

and then divide by 8 to get back to bytes:

((width * bpp + 31) & ~31) / 8

The padding can be computed by

int padding = stride - (((width * bpp) + 7) / 8)

The naive formula would be

stride - ((width * bpp) / 8)

But width * bpp might not align on a byte boundary and when it doesn't this formula would over count the padding by a byte. (Think of a 1 pixel wide bitmap using 1 bpp. The stride is 4 and the naive formula would say that the padding is 4 but in reality it is 3.) So we add a little bit to cover the case that width * bpp is not a byte boundary and then we get the correct formula given above.

share|improve this answer
 
Thank you, but how on earth did you come up with that expression? Why isn't it simply RenderWidth * 4? Isn't that the number of bytes for one line? – Mike Pateras Dec 31 '09 at 4:26
 
Sorry, I should have provided details. In your case you have bpp = 32 so yes the formula reduces toRenderWidth * 4. But there are odd cases (cheap LCDs use 18 bpp) and the fact that scanlines have to be aligned on 32-bit boundaries. I provided the general formula and an explanation of how to come up with it above. Hope it's elucidating. – Jason Dec 31 '09 at 5:09
 
Thank you. One more question. What does the tilde do on an integer like that? – Mike Pateras Dec 31 '09 at 5:23
 
@Mike Pateras: It is the bitwise not operator. That means that it flips the bits in the binary representation of the integer (so 0 becomes 1 and 1 becomes 0). I said that we want to ignore the lowest five bits ofwidth * bpp + 31 (and implicitly keep the rest). An easy way to do that is to make a number that has 0in those five bits and a 1 in the rest of the bits; this is called a bitmask. If we take the logical and width * bpp + 31 with this bitmask (~31) we have masked away the five low-order bits that we don't care about. Let me know if that isn't clear. – Jason Dec 31 '09 at 5:28
1  
I spent all day messing with this and your post helped me fix my code in 30 seconds. Thank you VERY much!!! and if anyone needs to know, ((width * 24 + 23) & ~23) / 8; works withPixelFormats.Rgb24;. – PiZzL3 Jan 10 '12 at 3:52

相關文章