How did I get a Stride less than the Bitmap width? - c#

I am copying from one 1bpp bitmap to a smaller 1bpp bitmap. I just want to clip out a region so I can count the number of black pixels.
I use the following to make the copies:
private Bitmap Copy(Bitmap srcBitmap, Rectangle section)
{
BitmapData SourceLockedData;
BitmapData DestLockedData;
Rectangle DestRect;
byte[] SrcImageData;
byte[] DestImageData;
int ByteCount;
int WidthCount = 0;
int CurrentLine = 0;
int DestStride;
int SrcStride = 0;
// Create the new bitmap and associated graphics object
Bitmap Newbmp = new Bitmap(section.Width, section.Height, PixelFormat.Format1bppIndexed);
Newbmp.SetResolution(srcBitmap.HorizontalResolution, srcBitmap.VerticalResolution);
//Lock the bits
SourceLockedData = srcBitmap.LockBits(section, ImageLockMode.ReadWrite, PixelFormat.Format1bppIndexed);
SrcStride = SourceLockedData.Stride;
//Get a count of the number of bytes to copy. Remember, bytes are not pixels.
ByteCount = SourceLockedData.Stride * SourceLockedData.Height;
//Initialize the source byte array
SrcImageData = new byte[ByteCount];
//Copy the data to the source byte array
Marshal.Copy(SourceLockedData.Scan0, SrcImageData, 0, ByteCount);
//Unlock the bits
srcBitmap.UnlockBits(SourceLockedData);
//Set a rectangle to the size of the New bitmap
DestRect = new Rectangle(new Point(0, 0), Newbmp.Size);
//Lock the bits
DestLockedData = Newbmp.LockBits(DestRect, ImageLockMode.ReadWrite, PixelFormat.Format1bppIndexed);
DestStride = DestLockedData.Stride;
//Get a count of the number of bytes to copy. Remember, bytes are not pixels.
ByteCount = DestLockedData.Stride * DestLockedData.Height;
//Initialize the source byte array
DestImageData = new byte[ByteCount];
//Copy the data to the destination byte array
Marshal.Copy(DestLockedData.Scan0, DestImageData, 0, ByteCount);
//Unlock for now
Newbmp.UnlockBits(DestLockedData);
for (int ArrayIndex = 0; ArrayIndex < ByteCount; ArrayIndex++)
{
if (WidthCount == Newbmp.Width)
{
//increment the line and push the index by the stride
ArrayIndex = (++CurrentLine) * DestStride;
continue;
}
DestImageData[ArrayIndex] = SrcImageData[ArrayIndex];
WidthCount++;
}
//Lock the bits again
DestLockedData = Newbmp.LockBits(DestRect, ImageLockMode.ReadWrite, PixelFormat.Format1bppIndexed);
//Copy data from byte array to IntPtr
Marshal.Copy(DestImageData, 0, DestLockedData.Scan0, ByteCount);
//Unlock bits
Newbmp.UnlockBits(DestLockedData);
// Return the bitmap
return Newbmp;
}
The biggest problem I am having is that both the SourceLockedData.Stride and DestLockedData.Stride are smaller than the width of the respective images. How can that be? From everything I know about stride it's the number of bits from one scan line of data to the next scan line of data. How is it mathematically possible for this to be less than the width?
Am I using LockBits or BitmapData wrong? Can BitmapData not be trusted? Should I calculate the stride by hand?
Tom P.

I figured out that the stride can be less than the width if you are dealing with RLE bitmaps. Since the bitmaps that I am loading are TIFFs they are RLE8 encoded.

Related

Receiving double image with bitmap on C#

I'm trying to use the SDK of a sensor for intraoral x-ray. I managed to save the image, but the image doubled.
This is the function to save the image.
private void SaveImage(short[] data, int widht, int height)
{
Bitmap pic = new Bitmap(widht, height);
Rectangle dimension = new Rectangle(0, 0, pic.Width, pic.Height);
BitmapData picData = pic.LockBits(dimension, ImageLockMode.ReadWrite, pic.PixelFormat);
IntPtr pixelStartAddress = picData.Scan0;
Marshal.Copy(data, 0, pixelStartAddress, data.Length);
pic.UnlockBits(picData);
pic.Save(#"C:\Users\WIM\Desktop\teste\teste\teste.jpeg", ImageFormat.Jpeg);
}
And this is the image saved
I'm not used to coding in C#. So I'm not sure if I do this right, and if the problem is on the function where i save the image or on the way as populated short array (short[] data);
[Update]
As suggested, I added PixelFormat.Format16bppGrayScale. and the function was like this:
private void SaveImage(short[] data, int widht, int height)
{
Bitmap pic = new Bitmap(widht, height, PixelFormat.Format16bppGrayScale);
Rectangle dimension = new Rectangle(0, 0, pic.Width, pic.Height);
BitmapData picData = pic.LockBits(dimension, ImageLockMode.ReadWrite, pic.PixelFormat) ;
IntPtr pixelStartAddress = picData.Scan0;
Marshal.Copy(data, 0, pixelStartAddress, data.Length);
pic.UnlockBits(picData);
pic.Save(#"C:\Users\WIM\Desktop\teste\teste\teste.jpeg", ImageFormat.Jpeg);
}
And I got this error:
System.Runtime.InteropServices.ExternalException: 'GDI+ generic error.'
In this line:
pic.Save(#"C:\Users\WIM\Desktop\teste\teste\teste.jpeg", ImageFormat.Jpeg);
[Update]
I tried to modify the pixel format, and I tested all the other 16bpp. This is the result:
Format16bpp565
Format16bpp555
And for Format16bppArgb1555 result a transparent image.
The pixel format Format16bppGrayScale exists in System.Drawing, but it is not actually implemented or supported. That's why it gives an error when trying to save it.
If this is indeed 16 bpp greyscale data, the only way to solve this in System.Drawing namespace is to reduce your data from 16-bit to 8-bit, treat it as an indexed (paletted) image, and just give it a grey fade from [0,0,0] to [255,255,255] as colour palette.
public static void SaveImage(short[] data, int width, int height, string path)
{
int dataLen = data.Length;
// This might not equal the width.
int inputStride = dataLen / height;
byte[] sourceData = new byte[dataLen];
// Reduce Int16 to bytes by downshifting the data by 8 bits.
// Effectively this divides the number by 256.
for (int i = 0; i < dataLen; ++i)
sourceData[i] = (Byte) (data[i] >> 8);
// Make an 8bpp image
using (Bitmap pic = new Bitmap(width, height, PixelFormat.Format8bppIndexed))
{
BitmapData picData = pic.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, pic.PixelFormat);
// Get actual stride from BitmapData
Int32 targetStride = picData.Stride;
Int64 scan0 = picData.Scan0.ToInt64();
// Stride is ALWAYS rounded up to the next multiple of 4, so copy line by line to avoid issues.
for (int y = 0; y < height; ++y)
Marshal.Copy(sourceData, y * inputStride, new IntPtr(scan0 + y * targetStride), width);
pic.UnlockBits(picData);
// Requesting Bitmap.Palette makes a COPY, so get it out, edit it, and then reassign it.
ColorPalette pal = pic.Palette;
for (int i = 0; i < 256; ++i)
pal.Entries[i] = Color.FromArgb(i, i, i);
pic.Palette = pal;
pic.Save(path, ImageFormat.Png);
}
}

Filling picxturebox with byte array causes strange effect

I'm writing in c# and using this to fill my PictureBox with a byte array
var bmp = new Bitmap(48, 32, PixelFormat.Format1bppIndexed);
var bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height),
ImageLockMode.WriteOnly, bmp.PixelFormat);
Marshal.Copy(fileArray, 0, bmpData.Scan0, fileArray.Length);
bmp.UnlockBits(bmpData);
return bmp;
The result is very strange.
It only fills up to the 125th byte. So I tried playing with the data and the 0-125 bytes show as they should but anything after byte 125 is not shown and it overwrites byte 125.
So if I do 0-125 all 0xff I get as solid bar up top. but adding 126 as 0x00 it replaces 125 with 0x00.
Instead of inserting the padding into your data you can move the data row by row:
int w = 48;
int h = 32;
int unpadded = w / 8; // unpadded byte length of one line in your data
Bitmap bmp = new Bitmap(w, h, PixelFormat.Format1bppIndexed);
BitmapData bmpData = bmp.LockBits(
new Rectangle(0, 0, bmp.Width, bmp.Height),
ImageLockMode.WriteOnly, bmp.PixelFormat);
int stride = 8 // padded length of scanline
for (int i = 0; i < h; i++)
{
Marshal.Copy(blob, i * unpadded , bmpData.Scan0 + i * stride, unpadded );
}
bmp.UnlockBits(bmpData);
return bmp;
Note that to make bitmap operations faster all physical rows must have a length that's a multiple of 4 bytes. The resulting, padded length is called stride.
The stride is the width of a single row of pixels (a scan line),
rounded up to a four-byte boundary. If the stride is positive, the
bitmap is top-down. If the stride is negative, the bitmap is
bottom-up.
With a logical length of 48 pixels and 1bpp your logical length is 6 bytes, so it must be padded to 8 bytes internally.
Best not to move your data in managed code to prepare this but to move only the right portions to the right slots..
Here are more examples of calculating stride for other formats
This solution worked for me. Its by far not the best and shows a lack of understanding or simply bitmaps can not support monochrome 1 bit per pixel images. Never the less it works. All this does is adds 2 bytes on each row of 6 bytes. So that the stride can deal with it.
int j = 1;
int k = 0;
for (int i = 0; i < 192; i++)
{
fixArray[k] = blob[i];
j++;
if (j == 7)
{
j = 1;
fixArray[++k] = 0;
fixArray[++k] = 0;
}
k++;
}

Converting RAW byte data to Bitmap

I am taking a screenshot of an android device using ADB and receiving the screenshot as raw byte data.
I know the raw byte data coming through is in the format rgba
Red is offset 0, Green offset 8, Blue offset 16, Alpha offset 24, each value is 1 byte. This makes up the entire byte array.
I am trying to convert this byte array to a Bitmap in C# and it is working for the most part, the image looks correct in every way apart from the fact that it is coming through with a 'blue hue' -- the coloring is off.
The following is the code I'm using to convert my raw byte data:
int WriteBitmapFile(string filename, int width, int height, byte[] imageData)
{
using (var stream = new MemoryStream(imageData))
using (var bmp = new Bitmap(width, height, PixelFormat.Format32bppArgb))
{
BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0,
bmp.Width,
bmp.Height),
ImageLockMode.WriteOnly,
bmp.PixelFormat);
IntPtr pNative = bmpData.Scan0;
Marshal.Copy(imageData, 0, pNative, imageData.Length);
bmp.UnlockBits(bmpData);
bmp.Save(filename);
}
return 1;
}
I've read in this post that it could be to do with the byte order of the actual rgba values. As you can see in the code above, I tried casting my bmpData.Scan0 to int* and it is still coming through with a blue hue.
I'm wracking my brain as to what I can do now to get this image to come through with the correct colors. I'm assuming it's reading red as blue and blue as red or vice versa.
I thought I could manipulate the raw byte data so that it is in the correct byte order when converting it to a bitmap, however I'm not sure how I can go about doing that.
Any suggestions?
So, in the end my solution was simple.
int WriteBitmapFile(string filename, int width, int height, byte[] imageData)
{
byte[] newData = new byte[imageData.Length];
for(int x = 0; x < imageData.Length; x+= 4)
{
byte[] pixel = new byte[4];
Array.Copy(imageData, x, pixel, 0, 4);
byte r = pixel[0];
byte g = pixel[1];
byte b = pixel[2];
byte a = pixel[3];
byte[] newPixel = new byte[] { b, g, r, a };
Array.Copy(newPixel, 0, newData, x, 4);
}
imageData = newData;
using (var stream = new MemoryStream(imageData))
using (var bmp = new Bitmap(width, height, PixelFormat.Format32bppArgb))
{
BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0,
bmp.Width,
bmp.Height),
ImageLockMode.WriteOnly,
bmp.PixelFormat);
IntPtr pNative = bmpData.Scan0;
Marshal.Copy(imageData, 0, pNative, imageData.Length);
bmp.UnlockBits(bmpData);
bmp.Save(filename);
}
return 1;
}
All I had to do was loop through the imageData and adjust the bytes that made up the pixels and re-order them so that they would suit the expected format for windows bitmap which is BGRA.
Obviously, I can still make some small optimisations in the for loop that shifts the bytes around, but it's working.
Did you have a look at this post ?
You're stating that every 'value' is 8 bytes, so 1 pixel is 4x8 = 32 bytes? But you are using 32bpp image format, so 32 bits per pixel -> 1 pixel = 4 bytes.
Also pay attention to little/big endian, if you're acquiring the image from another processor/network/...

Converting bitmap to special gray scale byte array

I need to create an image in memory (can be huge image!) and to extract from it byte array in the size of width x height. Each byte must have value of 0-255 (256 gray scale values: 0 for white and 255 for black).
The part of creating the image is easy, here is a simple example of my code:
img = new Bitmap(width, height);
drawing = Graphics.FromImage(img);
drawing.Clear(Color.Black);// paint the background
drawing.DrawString(text, font, Brushes.White, 0, 0);
Problem is to convert it to "my" special gray scale byte array. When I'm using any pixel format other then Format8bppIndexed, the byte array I'm getting from the bitmap is not in the size I need (width*length) so I need a conversion that takes too much time. When I'm using Format8bppIndexed I'm getting the byte array very fast and in the right size, but each byte/pixel is 0-15.
Changing the bitmap palette has no affect:
var pal = img.Palette;
for (int i = 1; i < 256; i++){
pal.Entries[i] = Color.FromArgb(255, 255, 255);
}
img.Palette = pal;
Any idea how to do it?
Edit: Full code:
// assume font can be Times New Roman, size 7500!
static private Bitmap DrawText(String text, Font font)
{
//first, create a dummy bitmap just to get a graphics object
var img = new Bitmap(1, 1);
var drawing = Graphics.FromImage(img);
//measure the string to see how big the image needs to be
var textSize = drawing.MeasureString(text, font);
//free up the dummy image and old graphics object
img.Dispose();
drawing.Dispose();
//create a new image of the right size (must be multiple of 4)
int width = (int) (textSize.Width/4) * 4;
int height = (int)(textSize.Height / 4) * 4;
img = new Bitmap(width, height);
drawing = Graphics.FromImage(img);
// paint the background
drawing.Clear(Color.Black);
drawing.DrawString(text, font, Brushes.White, 0, 0);
var bmpData = img.LockBits(new Rectangle(0, 0, img.Width, img.Height), ImageLockMode.ReadOnly, PixelFormat.Format8bppIndexed);
var newBitmap = new Bitmap(width, height, bmpData.Stride, PixelFormat.Format8bppIndexed, bmpData.Scan0);
drawing.Dispose();
return newBitmap;
}
private static byte[] GetGrayscleBytesFastest(Bitmap bitmap)
{
BitmapData bmpdata = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, bitmap.PixelFormat);
int numbytes = bmpdata.Stride * bitmap.Height;
byte[] bytedata = new byte[numbytes];
IntPtr ptr = bmpdata.Scan0;
Marshal.Copy(ptr, bytedata, 0, numbytes);
bitmap.UnlockBits(bmpdata);
return bytedata;
}
You probably want to do this in two steps. First, create a 16bpp grayscale copy of your original image as described in Convert an image to grayscale.
Then, create your 8bpp image with the appropriate color table and draw the 16bpp grayscale image onto that image. That will do the conversion for you, converting the 16-bit grayscale values to your 256 different colors.
You should then have an 8bpp image with your 256 different shades of gray. You can then call LockBits to get access to the bitmap bits, which will be index values in the range 0 to 255.
I have solved this problem with ImageSharp
I calculate the gray value from the rgb values and then add it to the array.
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
private static byte[] GetImageData(byte[] imageData)
{
using (var image = Image.Load<Rgba32>(imageData))
{
var buffer = new byte[image.Width * image.Height];
var index = 0;
image.ProcessPixelRows(accessor =>
{
for (int y = 0; y < accessor.Height; y++)
{
Span<Rgba32> pixelRow = accessor.GetRowSpan(y);
for (int x = 0; x < pixelRow.Length; x++)
{
ref Rgba32 pixel = ref pixelRow[x];
buffer[index] = (byte)((pixel.R + pixel.G + pixel.B) / 3);
index++;
}
}
});
return buffer;
}
}

Problems with Lockbits and GetPixels

I am trying to get all the pixels in an image using a Bitmap and GetPixels. Now I know that it is very inefficient so I have been looking into LockBits. I have successfully made what I think locks the bits but I can not get each and every pixels. My Code so far is...
//Creates Rectangle for holding picture
Rectangle bmpRec = new Rectangle(0, 0, bmp.Width, bmp.Height);
BitmapData bmpData = bmp.LockBits(bmpRec, ImageLockMode.ReadWrite, bmp.PixelFormat);
IntPtr Pointer = bmpData.Scan0; //Scans the first line of data
int DataBytes = Math.Abs(bmpData.Stride) * bmp.Height; //Gets array size
byte[] rgbValues = new byte[DataBytes]; //Creates array
string Pix = " ";
Marshal.Copy(Pointer, rgbValues, 0, DataBytes); //Copies of out memory
bmp.UnlockBits(bmpData);
for (int p = 0; p < DataBytes; p++)
{
Pix += " " + rgbValues[p];
}
I would like to use Lockbits, as it is the best way to get pixels. Any help?
Thank you.
When accessing the data directly from LockBits, the storage order is B-G-R, not RGB... that is why you are getting the values in the reverse order.

Categories

Resources