2

I'm using c sharp, .net 4 (client profile, if that's important) and I have a byte array that contains the raw data of an image. Specifically, this image:

good image

It is the output from the SANE test backend and the format is fully described on the SANE web site here. Incidentally, I have passed in the parameters:

  • depth: 8
  • mode: Color

and it has returned:

  • format: RGB
  • depth: 8
  • lines: 196
  • pixels per line: 157
  • bytes per line: 471
  • a byte stream that is 92316 bytes long

So, the numbers seem reasonable (196 * (157 * 471) = 92316) - three bytes (24bits) per pixel.

And from reading the SANE documentation the data is sequenced three bytes per pixel from the top left corner going left to right, top to bottom - like this (they have a better picture sorry for this ASCIItastic approach):

red,green,blue   red,green,blue  
--------------   --------------
    byte 1           byte 2       ...

Since I know so much about the image I figured that it would be super simps to load it up into a Bitmap and I knocked up this:

var bmp = new Bitmap(157, 196, PixelFormat.Format24bppRgb);
BitmapData bmpData = bmp
    .LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height),
              ImageLockMode.ReadWrite,
              bmp.PixelFormat);
Marshal.Copy(data, 0, bmpData.Scan0, data.Length);
bmp.UnlockBits(bmpData);

but it produced this:

bad image

close, but no cigar, so to speak.

So, what have I done wrong?

kmp
  • 10,535
  • 11
  • 75
  • 125

2 Answers2

4

The problem is that in a bitmap there is a gap between the bytes of a line and the bytes of the next line (more here).If speed is not your primary concern, you can do this much more easily with SetPixel and a loop.

EDIT:
This would give a significant speed up compared to SetPixel, but I'm afraid but you'd still have to use a loop ;)

for(int i = 0; i < bmp.Height; i++) {
    Marshal.Copy(data, 
        i * bmp.Height,
        bmpData.Scan0 + i * bmpData.Stride,
        bmp.Width * 3);
}

Note that I haven't tested the code, but it should be enough to give you the idea.

BlackBear
  • 22,411
  • 10
  • 48
  • 86
  • I would like it to be pretty performant - I think the images could easily get pretty big so looping and setting pixels isn't really ideal for me but thanks for your suggestion - I will keep it around in case I don't find a better way. – kmp Mar 06 '13 at 18:26
  • great, thanks - running that doesn't quite work though using your information, I figured out my own, really naive, way to do it that does produce the correct result – kmp Mar 06 '13 at 18:52
  • +1. [Stride](http://msdn.microsoft.com/en-us/library/system.drawing.imaging.bitmapdata.stride.aspx) is the right way to get size of each line! – Alexei Levenkov Mar 06 '13 at 21:19
3

Bitmap requires lines to be padded to some particular size (4 bytes, searc hor format details i.e. here ).

As result 157 pixels per line don't map exactly to binary format for bits of the bitmap and you see each next line shifted a bit. I think you need to allocate 158*3 bytes per line and copy each line from source (not exactly bitmap due to lack of padding) format to destination, filling last 3 bytes of each line with 0.

Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
  • I looked at the Stride property from @BlackBear and that suggested (to me at least) that I should use that to pad the row - in this case the stride length is 158, so I tried just sticking one extra 0 byte on the end of each row and it looks correct – kmp Mar 06 '13 at 18:43