I need to copy a bitmap that I receive from a camera into a BitmapSource in order to show it in a WPF application. Image arrives in PixelFormat.Format24bppRgb with a negative stride. I got this working by following code
//NOTE: image is in PixelFormat.Format24bppRgb
var bitmap = imageBuffer.Bitmap;
Image = new WriteableBitmap(bitmap.Width, bitmap.Height, 96, 96, PixelFormats.Rgb24, null);
var bitmapData = bitmap.LockBits(
new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height),
System.Drawing.Imaging.ImageLockMode.ReadOnly, bitmap.PixelFormat);
var rowSize = bitmapData.Stride < 0 ? -bitmapData.Stride : bitmapData.Stride;
var bitmapPtr = bitmapData.Scan0;
var bitmapLine = new Int32Rect(0, 0, bitmap.Width, 1);
for (int line = 0; line < bitmap.Height; line++)
{
Image.WritePixels(bitmapLine, bitmapPtr, rowSize, rowSize, 0, line);
bitmapPtr += bitmapData.Stride;
}
bitmap.UnlockBits(bitmapData);
The problem I am running into is that that blue and red channels seem to be swapped. I could resolve the issue by creating the BitmapSource as Bgr24 but since in application I need to also do some image processing prior to conversion I would prefer to have things in a correct format prior to that. Am I doing something wrong in the conversion or is this some GDI peculiarity?
Note that if I apply the camera bitmap directly to a WinForms picture box, the image is displayed correctly. Also WriteableBitmap is only recreated for the sake of code brevity.
If the image format you receive is RGB but the stride is negative, then the image format is BGR as it's being read backwards.
Negative stride means it's an image bottom-up instead of top-down, usually the draw operations of Graphics would handle these things for you, but the Image class WritePixels doesn't allow to specify a negative stride, so you must reverse the pixel format (BGR)
The correct conversion from System.Drawing.Imaging.PixelFormat.Format24bppRgb to System.Windows.Media.PixelFormat is PixelFormats.Bgr24.
Negative stride has nothing to do with with the pixel format, it only describes vertical rotation of the image - top down for positive and bottom up for negative. If the stride had any effect on the pixel format, then the code below would have stored the images with reversed stride with blue and red channel swapped. This is not the case and the only effect seen is that one image is vertically rotated.
var wholeImage = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
bitmapData = bitmap.LockBits(wholeImage, ImageLockMode.ReadOnly, bitmap.PixelFormat);
var reverseStride = -bitmapData.Stride;
var reversedStartPtr = bitmapData.Scan0 + bitmapData.Stride * (bitmapData.Height - 1);
var reverseStrideBitmap = new Bitmap(bitmapData.Width, bitmapData.Height,
reverseStride, bitmapData.PixelFormat, reversedStartPtr);
bitmap.Save("original.png");
reverseStrideBitmap.Save("reversedStride.png");
Related
I made an Automatic image thresholding function, and wanna to save it as a bitmap file.
However, when I use the Bitmap.Save function of C# GDI+, although I set the ImageFormat as BMP, it always as the RGB color image file but not bitmap image file.
I must save it as the bitmap image file for the printer only can read the bitmap image file.
Maybe you will ask me what the bitmap image file is. I am not an expert of image processing and sorry about that can hardly explain clearly. But I can quote an example: in Photoshop, there are several color mode, such as RGB mode/CMYK mode/Index mode/Grayscale mode/Bitmap mode, I want to save the image as the Bitmap mode in C#.
Here is what Adobe explain about the Bitmap mode in their website:
Bitmap mode uses one of two color values (black or white) to represent the pixels in an image. Images in Bitmap mode are called bitmapped 1‑bit images because they have a bit depth of 1.
I googled but found nothing about this. How can I do it in C#? Thank you.
Here is my code:
Thread T = new Thread(() => {
Bitmap processedBitmap = new Bitmap(#"G:\\0001.jpg");
BitmapData bitmapData = processedBitmap.LockBits(new Rectangle(0, 0, processedBitmap.Width, processedBitmap.Height), ImageLockMode.ReadWrite, processedBitmap.PixelFormat);
int bytesPerPixel = Bitmap.GetPixelFormatSize(processedBitmap.PixelFormat) / 8;
int byteCount = bitmapData.Stride * processedBitmap.Height;
byte[] pixels = new byte[byteCount];
IntPtr ptrFirstPixel = bitmapData.Scan0;
Marshal.Copy(ptrFirstPixel, pixels, 0, pixels.Length);
int heightInPixels = bitmapData.Height;
int widthInBytes = bitmapData.Width * bytesPerPixel;
for (int y = 0; y < heightInPixels; y++)
{
int currentLine = y * bitmapData.Stride;
for (int x = 0; x < widthInBytes; x = x + bytesPerPixel)
{
int oldBlue = pixels[currentLine + x];
int oldGreen = pixels[currentLine + x + 1];
int oldRed = pixels[currentLine + x + 2];
double averageColor = (oldBlue + oldGreen + oldRed) / 3;
int NewC;
if (averageColor > 200)
{
NewC = 255;
}
else
{
NewC = 0;
}
// calculate new pixel value
pixels[currentLine + x] = (byte)NewC;
pixels[currentLine + x + 1] = (byte)NewC;
pixels[currentLine + x + 2] = (byte)NewC;
}
}
// copy modified bytes back
Marshal.Copy(pixels, 0, ptrFirstPixel, pixels.Length);
processedBitmap.UnlockBits(bitmapData);
processedBitmap.Save("G:\\aaa.bmp", ImageFormat.Bmp);
MessageBox.Show("Sucess!");
});
T.Start();
I believe the OP is referring to the last type of image in this adobe link
Bitmap is merely a container for data, the format of the data that you are storing is defined by the PixelFormat setting. As can be seen "Adobe" Bitmap mode is a 2 color format mode and corresponds to PixelFormat.Format1bppIndexed in C# Bitmap.
You have a couple of constructors for Bitmaps which have the PixelFormat as a parameter.
1.
public Bitmap (int width, int height, System.Drawing.Imaging.PixelFormat format);
2.
public Bitmap (int width, int height, int stride, System.Drawing.Imaging.PixelFormat format, IntPtr scan0);
With your source image you have a 24 bit image.
When you do your colour averaging, you're writing back to the image buffer with the following code:
pixels[currentLine + x] = (byte)NewC;
pixels[currentLine + x + 1] = (byte)NewC;
pixels[currentLine + x + 2] = (byte)NewC;
You're writing back 24 bits again.
So for example if your original values for RGB were (202, 203, 249), then NewC would be 218, and then you threshold it back to 255, so you write back (255,255,255) which is still an RGB value, it's just for white.
Then you save that image using
processedBitmap.Save("G:\\aaa.bmp", ImageFormat.Bmp);
The ImageFormat class just sets the type of image, like jpeg, png, etc.
And as you've discovered, you still have a 24 bit image being output.
So what you want is to save the image as a pure 1 bit per pixel black and white image.
To do this you need to specify the PixelFormat of the image you're saving, and specifically you want the PixelFormat Format1bppIndexed.
If you instead change the relevant bit of your code to:
...
Marshal.Copy(pixels, 0, ptrFirstPixel, pixels.Length);
processedBitmap.UnlockBits(bitmapData);
Bitmap clone = processedBitmap.Clone(new Rectangle(0, 0, processedBitmap.Width, processedBitmap.Height), PixelFormat.Format1bppIndexed);
clone.Save("G:\\aaa.bmp", ImageFormat.Bmp);
MessageBox.Show("Success!");
Now your output clone will be a 1bpp image.
However, you can simplify your code even more, because this clone function can actually do all the work for you, and you can reduce your code to just the following.
Bitmap processedBitmap = new Bitmap(#"G:\0001.jpg");
Bitmap clone = processedBitmap.Clone(new Rectangle(0, 0, processedBitmap.Width, processedBitmap.Height), PixelFormat.Format1bppIndexed);
clone.Save("G:\\aaa.bmp", ImageFormat.Bmp);
MessageBox.Show("Success!");
Be aware though that the output is slightly different.
Here are some test samples of the output.
This is my input image:
Output image with your thresholding code:
And output image using just the clone method:
To save a BMP object to a file all you have to do it this:
bmp.Save("c:\\Path\\To\\File\\image.bmp, ImageFormat.Bmp);
Are you doing anything else?
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;
}
}
I am writing a Kinect application, where I use the color image from the sensor. I get a 640 x 480 color image, I copy the data from the sensor to a WriteableBitmap, with the WritePixels method. When I use the whole color image I have no issues. But I would like to use only the middle part of the image. But I can't get stride and or offset right?
To copy the whole image I do the following:
_colorImageWritableBitmap.WritePixels(
new Int32Rect(0, 0, colorImageFrame.Width, colorImageFrame.Height),
_colorImageData,
colorImageFrame.Width * Bgr32BytesPerPixel,
0);
As I mention I only want the middle part of the image. I would like to start at a width at 185px and take the next 270px, and stop there. And I use the the whole height.
My PixelFormat is bgr32, so to calculate the byte pr. pixel I use:
var bytesPrPixel = (PixelFormats.Bgr32.BitsPerPixel + 7)/8;
And my stride:
var stride = bytesPrPixel*width;
The writepixel method:
_colorImageWritableBitmap.WritePixels(
new Int32Rect(0, 0, colorImageFrame.Width, colorImageFrame.Height),
_colorImageData, stride, offset);
But when I change the width to other than 640, the image gets wrong (hidden in noise).
Can someone help me, to understand what I am doing wrong here?
You have to properly copy the pixels from the source bitmap. Assuming that the source colorImageFrame is also a BitmapSource, you would do it this way:
var width = 270;
var height = 480;
var x = (colorImageFrame.PixelWidth - width) / 2;
var y = 0;
var stride = (width * colorImageFrame.Format.BitsPerPixel + 7) / 8;
var pixels = new byte[height * stride];
colorImageFrame.CopyPixels(new Int32Rect(x, y, width, height), pixels, stride, 0);
Now you could write the pixel buffer to your WriteableBitmap by:
colorImageWritableBitmap.WritePixels(
new Int32Rect(0, 0, width, height), pixels, stride, 0);
Or instead of using WriteableBitmap, you just create a new BitmapSource, like:
var targetBitmap = BitmapSource.Create(
width, height, 96, 96, colorImageFrame.Format, null, pixels, stride);
However, the easiest way to create a crop of the source bitmap might be to used a CroppedBitmap like this:
var targetBitmap = new CroppedBitmap(
colorImageFrame, new Int32Rect(x, y, width, height));
I want to create an 800x600 image whose each pixel is randomly chosen to be either green or red. How can I do this in WPF?
See WriteableBitmap
const uint red = 0xFFFF0000,green = 0xFF00FF00;
var rnd = new Random();
var bmp = new WriteableBitmap(800, 600, 96, 96, PixelFormats.Pbgra32, null);
var data = Enumerable.Range(0, 800 * 600).Select(x => rnd.NextDouble() > 0.5 ? red : green).ToArray();
bmp.WritePixels(new Int32Rect(0, 0, 800, 600), data, bmp.BackBufferStride, 0);
That is a simple example and does not cover dealing with the bitmap stride, different pixel formats or alpha.
I am currently having the following problem: I want to convert a byte array that comes from a file with the following configuration:
Byte1: R color of pixel 0,0.
Byte2: G color of pixel 0,0.
Byte3: B color of pixel 0,0.
Byte4: R color of pixel 0,1.
...
ByteN: R color of pixel n,n.
So what I want to do is convert these bytes into a bitmap without having to set pixel by pixel with bitmap.setPixel because it takes too long.
Any suggestions? Thanks in advance!
If you have the byte[] of the pixels, and the width and height, then you can use BitmapData to write the bytes to the bitmap since you also know the format. Here's an example:
//Your actual bytes
byte[] bytes = {255, 0, 0, 0, 0, 255};
var width = 2;
var height = 1;
//Make sure to clean up resources
var bitmap = new Bitmap(width, height);
var data = bitmap.LockBits(new Rectangle(Point.Empty, bitmap.Size), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);
Marshal.Copy(bytes, 0, data.Scan0, bytes.Length);
bitmap.UnlockBits(data);
This is a very fast operation.
You will need to import these three namespaces at the top of your C# file, at minimum:
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;