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.
Related
I know this is mostly an image question not code, but I'll give it a shot here.
So first I have a 8bits/pixel grayscale image(bitmap). Which means that each pixel is represented into 1
byte. This means that the the pixel value is the byte value. Clear enough.
But then...
I have a 16bits/pixel grayscale image (bitmap). Which means that each pixel is represented into 2 bytes. This is clear for me. Now I create a byte[] array that will hold each byte value.
For an 1024x1024 image I will have 2097152 bytes. It's 1024*1024*2.
My question is now, how do I get the pixel value for a specific pixel.
Let's say for pixel at position(X|Y) the bytes are 84 and 77. How do I transform these 2 values into the pixel value.
Firstly I need this for some calculation where I need the pixel value, then I want to change the color palette of the bitmap and it works fine with 8 bitsperpixel images, but doesn't for 16bitsperpixel images.
Any help would be nice.
var width = bitmap.PixelWidth;
var height = bitmap.PixelHeight;
var dpiX = bitmap.DpiX;
var dpiY = bitmap.DpiY;
byte[] pixels = new byte[
bitmap.PixelHeight * bitmap.PixelWidth *
bitmap.Format.BitsPerPixel / 8];
bitmap.CopyPixels(pixels, bitmap.PixelWidth * bitmap.Format.BitsPerPixel / 8, 0);
This is how I create the array of pixels.
It may be easier to use a 2-byte type for the pixel array:
var width = bitmap.PixelWidth;
var height = bitmap.PixelHeight;
var pixels = new ushort[width * height];
bitmap.CopyPixels(pixels, 2 * width, 0);
Access an individual pixel value directly from that array:
var x = 100;
var y = 200;
var pixel = (int)pixels[width * y + x];
In order to convert the 16bpp pixel array into a 8bpp array, just divide each pixel value by 256
var pixels8bpp = pixels.Select(p => (byte)(p / 256)).ToArray();
and create a 8bpp BitmapSource by
var bitmap8bpp = BitmapSource.Create(
width, height, 96, 96, PixelFormats.Gray8, null, pixels8bpp, width);
Working in WPF and trying to create a BitmapSource from scratch. The following code gives an Argument Exception " Value does not fall within the expected range." I can't figure out why. I'm still new to WPF and maybe I'm not using the pixel format correctly?
int width = 12;
int height = 14;
byte[] colorData = new byte[width * height * 4];
for(int i = 0; i < colorData.Length; i++)
{
colorData[i] = 155; //create all pixels same shade of gray
}
BitmapSource bitmap = BitmapSource.Create(width, height, 96, 96, PixelFormats.Bgra32, null, colorData, width);
The last argument of the BitmapSource.Create method is the stride, i.e. the number of bytes per "scan line".
In case of a pixel format with four bytes per pixel, it is 4 * width:
var stride = 4 * width;
var bitmap = BitmapSource.Create(
width, height, 96, 96, PixelFormats.Bgra32, null, colorData, stride);
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");
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));