Saving a single channel of a bitmap image to a file [duplicate] - c#

This question already has answers here:
Split PNG into RGB and Alpha Channels
(2 answers)
Closed 3 years ago.
I'm using this code to save a bitmap as binary data.
Bitmap bmp = new Bitmap(screenWidth, position);
Graphics g = Graphics.FromImage(bmp);
g.CopyFromScreen(screenLeft, screenTop, 0, 0, bmp.Size);
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
System.Drawing.Imaging.BitmapData bmpData = bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);
IntPtr ptr = bmpData.Scan0;
int bytes = bmpData.Stride * bmp.Height;
byte[] rgbValues = new byte[bytes];
System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes);
bmp.UnlockBits(bmpData);
File.WriteAllBytes(filename, bmp);
g.Dispose();
As I only need the first channel's values, is it possible to retrieve that from the bitmap? Performance is essential.

You're almost there, but there are a few key details missing:
Instead of using bmp.PixelFormat, force the pixel format for the BitmapData object to PixelFormat.Format32BppArgb, then you're 100% sure what structure you will get, and in 32-bit mode, the stride will always exactly match a predictable width * 4. If you don't do this, you may get unexpected results if the read image happens to be paletted or some sort of 16bpp format where each pixel can't be divided into simple colour component bytes.
Loop over the data and extract the channel. The order of the letters 'ARGB' refers to the a hexadecimal value 0xAARRGGBB (like, for example, 0xFF428ED0), which is a little-endian Uint32 value, meaning the actual order of the colour component bytes is the reverse: { BB, GG, RR, AA }.
So, to extract your channel:
// Channels are: B=0, G=1, R=2, A=3
Int32 channel = 1 // for this example, extract the Green channel.
Int32 width;
Int32 height;
Byte[] rgbaValues;
using (Bitmap bmp = new Bitmap(screenWidth, position))
using (Graphics g = Graphics.FromImage(bmp))
{
width = bmp.Width
height = bmp.Height;
g.CopyFromScreen(screenLeft, screenTop, 0, 0, bmp.Size);
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
Int32 bytes = bmpData.Stride * bmp.Height;
rgbaValues = new byte[bytes];
Marshal.Copy(bmpData.Scan0, rgbValues, 0, bytes);
bmp.UnlockBits(bmpData);
g.Dispose();
}
Byte[] channelValues = new byte[width * height];
Int32 lineStart = 0;
Int32 lineStartChannel = 0;
for (Int32 y = 0; y < height; ++y)
{
Int32 offset = lineStart;
Int32 offsetChannel = lineStartChannel;
for (Int32 x = 0; x < width; ++x)
{
// For reference:
//Byte blue = rgbaValues[offset + 0];
//Byte green = rgbaValues[offset + 1];
//Byte red = rgbaValues[offset + 2];
//Byte alpha = rgbaValues[offset + 3];
channelValues[offsetChannel] = rgbaValues[offset + channel];
offset += 4;
offsetChannel++;
}
lineStart += stride;
lineStartChannel += width;
}
File.WriteAllBytes(filename, channelValues);
This just saves the data as byte array. If you want to write it as image, the simplest way is probably to make an 8-bit bitmap, open a BitmapData object on it, and write the lines into it one by one, and then set its colour palette to a generated range from 0,0,0 to 255,255,255.
I posted a function that takes a byte array, image dimensions and a palette and makes an image out of it in this answer.

Related

Slanted bitmap, stride calculation for RGB565 C#

Some of my resulting images are slanted, some are not.
Expected Result: (529x22)
Actual Result: (529x22)
Don't mind the different image sizes, these are screenshots. They are both 529x22.
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):
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.
This expects the stride to correspond to the width, without padding. There can be padding. The padding would "eat" some of the next line, which will therefore appear to shift left.
Since the padding breaks up the lines, the only real way to deal with it (other than using the same padding everywhere) is copying line by line. You can calculate the starting address of a line with bmpData.Scan0 + y * bmpData.Stride. Copy starting there, for every y.
// bytes => not using this because it gives error
Yes, because your array does not have padding. So you were telling it to copy more data than the array held.

treshould filter in Aforge doesn't seem to work properly

hope you all doing well. I did write a bit of codes in C# using Aforge library. I wanted to crop my main image captured from webcam so as to have a nice ROI. When I use threshold value of 0 everything should be in white pixels (total of lets say 26880 pixels) but it seems that I have some black pixels (578 pixels) within my cropped image. any idea of what may caused it? when I don't crop my image everything is fine.
Bitmap img = (Bitmap)eventArgs.Frame.Clone();
Bitmap bmp = new Bitmap(x2box, y2box);
bmp = img.Clone(new Rectangle(x1box, y1box, x2box, y2box), eventArgs.Frame.PixelFormat);
Grayscale filter = new Grayscale(0.2125, 0.7154, 0.0721);
Bitmap img1 = filter.Apply(bmp);
Threshold tresh = new Threshold((int)tresh1); // tresh1 is 0-255 but is set to zero here
tresh.ApplyInPlace(img1);
int iterator = 1; int xrow = 0; // here i use these constant to calculate location of the pixels
byte[] arraybyte = BitmapToByteArray(img1);
for (int i = 0; i < arraybyte.Length; i++)
{
if (i - iterator * img1.Width == 0)
{
xrow++;
iterator++;
}
if (arraybyte[i] == 0) // if pixel is black
{
X_val.Add(i - xrow * img1.Width);
Y_val.Add(iterator);
}
}
for (int i = 0; i < X_val.Count; i++)
{
YAve += Y_val[i];
XAve += X_val[i];
}
MessageBox.Show(X_val.Count.ToString()); // shows non-zero value!
the BitmapToByteArray method is as follow:
public static byte[] BitmapToByteArray(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;
}
The number of bytes for each row of the Bitmap will be enforced to be a multiple of 4. If roi width * bytes per pixel is not a multiple of 4, you will have padding bytes at the end of each row.
They will not be thresholded as they aren't really part of the Bitmap, so their value may be 0. Your BitmapToByteArray method might not be padding-aware and read every byte.

Convert RGB8 byte[] to Bitmap

I have raw pixel data coming from a camera in RGB8 format which I need to convert to a Bitmap. However, the Bitmap PixelFormat only seems to support RGB 16, 24, 32, and 48 formats.
I attempted to use PixelFormat.Format8bppIndexed, but the image appears discolored and inverted.
public static Bitmap CopyDataToBitmap(byte[] data)
{
var bmp = new Bitmap(640, 480, PixelFormat.Format8bppIndexed);
var bmpData = bmp.LockBits(
new Rectangle(0, 0, bmp.Width, bmp.Height),
ImageLockMode.WriteOnly, bmp.PixelFormat);
Marshal.Copy(data, 0, bmpData.Scan0, data.Length);
bmp.UnlockBits(bmpData);
return bmp;
}
Is there any other way to convert this data type correctly?
This creates a linear 8-bit grayscale palette in your image.
bmp.UnlockBits(bmpData);
var pal = bmp.Palette;
for (int i = 0; i < 256; i++) pal.Entries[i] = Color.FromArgb(i, i, i);
bmp.Palette = pal;
return bmp;
You will still need to invert the scan lines, maybe like this:
for (int y = 0; y < bmp.Height; y++)
Marshal.Copy(data, y * bmp.Width,
bmpData.Scan0 + ((bmp.Height - 1 - y) * bmpData.Stride), bmpData.Stride);

How to display a 12-bit image in C# [duplicate]

This question already has an answer here:
How to load 12 bit image into System.Drawing.Bitmap?
(1 answer)
Closed 9 years ago.
I succeeded in displaying an 8-bit bitmap image in C# by using System.Drawing.Imaging.PixelFormat.Format8bppIndexed.
However, I now have a 12-bit raw data image. How do I display it in C#.
I successed in displaying the received 12-bit pixel values by down scaling them to 8-bit pixel values as followings:
...
// Receive bytes from a Device
board.ReadBytes((int)0xA0, 1024 * 30, ref data_Array1);
//restore the 12-bit pixel values
for (int i = 0; i < width * height; i++)
{
// This is a 12-bit pixel value.
raw_data1[i] = (ushort)((((int)data_Array1[2 * i+1] << 8) & 0x0F00) | ((int)data_Array1[2 * i ] & 0xFF));
}
// Convert from a 12-bit array to a 8-bit array.
raw_data_byte1 = DownScale(raw_data1);
//create a new Bitmap
Bitmap bmp = new Bitmap(height, width, System.Drawing.Imaging.PixelFormat.Format8bppIndexed); System.Drawing.Imaging.ColorPalette pal = bmp.Palette;
//create grayscale palette
for (int i = 0; i < 256; i++)
{
pal.Entries[i] = Color.FromArgb((int)255, i, i, i);
}
//assign to bmp
bmp.Palette = pal;
//lock it to get the BitmapData Object
System.Drawing.Imaging.BitmapData bmData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format8bppIndexed);
//copy the bytes
System.Runtime.InteropServices.Marshal.Copy(raw_data_byte1, 0, bmData.Scan0, bmData.Stride * bmData.Height);
//never forget to unlock the bitmap
bmp.UnlockBits(bmData);
//display
this.pictureBox1.Image = bmp;

How to create a bmp file from byte[] in C#

I have a byte[] array received in TCP Client.The array contains a 24-bit RGB Bitmap file.How to create that bitmap file with given Width ,Height and data?
In C++ I use this
int WriteBitmapFile(const char *filename, int width, int height, unsigned char *imageData)
{
FILE *filePtr; // file pointer
BITMAPFILEHEADER bitmapFileHeader; // bitmap file header
BITMAPINFOHEADER bitmapInfoHeader; // bitmap info header
DWORD imageIdx; // used for swapping RGB->BGR
unsigned char tempRGB; // used for swapping
// open file for writing binary mode
filePtr = fopen(filename, "wb");
if (!filePtr)
return 0;
// define the bitmap file header
bitmapFileHeader.bfSize = sizeof(BITMAPFILEHEADER);
bitmapFileHeader.bfType = 0x4D42;
bitmapFileHeader.bfReserved1 = 0;
bitmapFileHeader.bfReserved2 = 0;
bitmapFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
// define the bitmap information header
bitmapInfoHeader.biSize = sizeof(BITMAPINFOHEADER);
bitmapInfoHeader.biPlanes = 1;
bitmapInfoHeader.biBitCount = 32; // 24-bit
bitmapInfoHeader.biCompression = BI_RGB; // no compression
bitmapInfoHeader.biSizeImage = width * abs(height) * 4; // width * height * (RGB bytes)
bitmapInfoHeader.biXPelsPerMeter = 0;
bitmapInfoHeader.biYPelsPerMeter = 0;
bitmapInfoHeader.biClrUsed = 0;
bitmapInfoHeader.biClrImportant = 0;
bitmapInfoHeader.biWidth = width; // bitmap width
bitmapInfoHeader.biHeight = height; // bitmap height
// switch the image data from RGB to BGR
for(imageIdx = 0; imageIdx < bitmapInfoHeader.biSizeImage; imageIdx+=4)
{
tempRGB = imageData[imageIdx];
imageData[imageIdx] = imageData[imageIdx + 2];
imageData[imageIdx + 2] = tempRGB;
}
// write the bitmap file header
fwrite(&bitmapFileHeader, 1, sizeof(BITMAPFILEHEADER), filePtr);
// write the bitmap info header
fwrite(&bitmapInfoHeader, 1, sizeof(BITMAPINFOHEADER), filePtr);
// write the image data
fwrite(imageData, 1, bitmapInfoHeader.biSizeImage, filePtr);
// close our file
fclose(filePtr);
// Success
return 1;
}
How could I do that in C#?
If the array actually contains a bitmap file, then you can just save the bytes as a file:
File.WriteAllBytes(fileName, imageData);
If the array contains only raw pixel data, you can create a Bitmap object using the data:
unsafe {
fixed (byte* ptr = imageData) {
using (Bitmap image = new Bitmap(width, height, stride, PixelFormat.Format24bppRgb, new IntPtr(ptr))) {
image.Save(fileName);
}
}
}
The stride value is the number of bytes between the scan lines. If there is no padding between the scan lines, it's width * 3 for a 24bpp format.
This method uses the data in the array without creating another copy of the entire image in memory (which is why it needs the stride value).
If the bitmap data is stored upside down in the array, the stride value should be negative, and the pointer should be the start of the last scan line in memory (ptr + stride * (height - 1)).
I can't test it using the stream you will be receiving, but this should work.
int WriteBitmapFile(string filename, int width, int height, byte[] imageData)
{
using (var stream = new MemoryStream(imageData))
using (var bmp = new Bitmap(width, height, PixelFormat.Format24bppRgb))
{
BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0,
bmp.Width,
bmp.Height),
ImageLockMode.WriteOnly,
bmp.PixelFormat);
Marshal.Copy(imageData, 0, bmpData.Scan0, imageData.Length);
bmp.UnlockBits(bmpData);
bmp.Save(filename);
}
return 1;
}
I'd recommend making a Bitmap in C#, and letting it save itself.
For an example, see this post. (Particularly, the last response is correct.)
this is one way of doing it, here i have created a custom Event args that contains the size at which the image was stored as a byte array. You may not need to bother with this, this was code i created to retreive images from a byte array that a gige camera was storing to so for me this made sence.
public Bitmap ShowImage(byte[] sender, EventImageParams e)
{
Bitmap bitmap = new Bitmap(e.width, e.height, PixelFormat.Format24bppRgb);
BitmapData bmData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height),
ImageLockMode.ReadWrite, bitmap.PixelFormat);
IntPtr pNative = bmData.Scan0;
Marshal.Copy(sender, 0, pNative, (e.width * e.height * 3));
//
bitmap.UnlockBits(bmData);
return bitmap;
}

Categories