I'm trying to write out a grayscale image using Lockbits, my current code looks is
/// <summary>
/// Save the content of the FrameProc out to a bitmap
/// </summary>
public void Save(string path)
{
Bitmap bmp = new Bitmap(this.size.Width, this.size.Height
,PixelFormat.Format32bppRgb);
var data = bmp.LockBits(this.size, ImageLockMode.WriteOnly, bmp.PixelFormat);
unsafe
{
for (int y = 0; y < this.size.Height; y++)
{
byte* row = (byte*)data.Scan0 + (y * data.Stride);
for (int x = 0; x < this.size.Width; x++)
{
byte value = (byte)this.buffer[y, x];
row[x*Bits+r] = value;
row[x*Bits+g] = value;
row[x*Bits+b] = value;
}
}
}
bmp.UnlockBits(data);
bmp.Save(path, ImageFormat.Bmp);
}
where
/// <summary>
/// The amount of Bytes per pixel in the image
/// </summary>
private const int Bits = 4;
/// <summary>
/// Image components
/// </summary>
private const int a=3, r = 2, g = 1, b = 0;
However the image i receive is not correct:
Maybe this is related to how i'm reading them in? So here's that code
public FrameProc(Bitmap bmp)
{
this.size=new Rectangle(new Point(0,0), bmp.Size);
var data = bmp.LockBits(this.size
,ImageLockMode.ReadOnly
,bmp.PixelFormat);
this.buffer = new Matrix(this.size.Height, this.size.Width);
unsafe
{
for (int y = 0; y < this.size.Height; y++)
{
byte* row = (byte*)data.Scan0 + (y * data.Stride);
for (int x = 0; x < this.size.Width; x++)
{
this.buffer[y,x] = 0.299*row[x*Bytes+r]
+ 0.587*row[x*Bytes+g]
+ 0.114*row[x*Bytes+b];
}
}
}
bmp.UnlockBits(data);
}
From the results you're getting - it looks exactly as if each pixel is three bytes big and not four as you have declared it - and as one would expect. (Note: you called it Bits - but that's wrong - it should be namned Bytes, not Bits).
I'd experiment with any one of this:
change from 4 to 3 bytes
change from Format32bppRgb to Format32bppArgb and fill out the alpha with 255
change from 4 to 3 bytes and from Format32bppRgb to from Format24bppRgb
I would also rewrite the loop slightly for performance (sorry, I can't help myself):
for (int x = 0; x < this.size.Width; x++, row += Bits)
{
byte value = (byte)this.buffer[y, x];
row[r] = value;
row[g] = value;
row[b] = value;
}
But were you really would get more speed if you get a pointer to this.buffer using the fixed keyword. Yes, you're not having any performance problems, but I couldn't help myself from mentioning it!
Use this function indeed:
public Bitmap MakeGrayscale(Bitmap original)
{
unsafe
{
//create an empty bitmap the same size as original
Bitmap newBitmap = new Bitmap(original.Width, original.Height);
//lock the original bitmap in memory
BitmapData originalData = original.LockBits(
new Rectangle(0, 0, original.Width, original.Height),
ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
//lock the new bitmap in memory
BitmapData newData = newBitmap.LockBits(
new Rectangle(0, 0, original.Width, original.Height),
ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);
//set the number of bytes per pixel
// here is set to 3 because I use an Image with 24bpp
int pixelSize = 3;
for (int y = 0; y < original.Height; y++)
{
//get the data from the original image
byte* oRow = (byte*)originalData.Scan0 + (y * originalData.Stride);
//get the data from the new image
byte* nRow = (byte*)newData.Scan0 + (y * newData.Stride);
for (int x = 0; x < original.Width; x++)
{
//create the grayscale version
byte grayScale =
(byte)((oRow[x * pixelSize] * .11) + //B
(oRow[x * pixelSize + 1] * .59) + //G
(oRow[x * pixelSize + 2] * .3)); //R
//set the new image's pixel to the grayscale version
nRow[x * pixelSize] = grayScale; //B
nRow[x * pixelSize + 1] = grayScale; //G
nRow[x * pixelSize + 2] = grayScale; //R
}
}
//unlock the bitmaps
newBitmap.UnlockBits(newData);
original.UnlockBits(originalData);
return newBitmap;
}
}
Source and other interesting examples (with theory behind) could be taken from here
Related
I'm currently writing an ASCOM driver which is used to take picture with a SigmaFP camera.
Everything works fine but I'm currently hitting a wall when I have to convert my DNG to a standard image array for ASCOM (https://ascom-standards.org/Help/Developer/html/P_ASCOM_DriverAccess_Camera_ImageArray.htm)
With libraw I convert my DNG file, convert it to a bitmap and save it as a new jpeg file.
But, when I try to conver this jpeg file to an array (using Bitmap and my custom ReadBitmap function) I get this kind of pictures :
output image
Here is the input image
I don't know what I have missed during those steps
Here is my ReadBitmap function :
public int[,] ReadBitmap(Bitmap img)
{
BitmapData data = img.LockBits(new Rectangle(0, 0, img.Width, img.Height), ImageLockMode.ReadOnly, img.PixelFormat);
IntPtr ptr = data.Scan0;
int bytesCount = Math.Abs(data.Stride) * img.Height;
var result = new int[img.Width, img.Height];
byte[] bytesArray = new byte[bytesCount];
Marshal.Copy(ptr, bytesArray, 0, bytesCount);
img.UnlockBits(data);
var width = img.Width;
var height = img.Height;
Parallel.For(0, width * height, rc =>
{
var b = bytesArray[rc * 3];
var g = bytesArray[rc * 3 + 1];
var r = bytesArray[rc * 3 + 2];
int row = rc / width;
int col = rc - width * row;
//var rowReversed = height - row - 1;
result[col, row] = (int)(0.299 * r + 0.587 * g + 0.114 * b);
}
);
return result;
}
Here is the method used to convert DNG to jpeg (which will be used to get the int array)
IntPtr data = LoadRaw(path);
NativeMethods.libraw_dcraw_process(data);
// extract raw data into allocated memory buffer
var errc = 0;
var ptr = NativeMethods.libraw_dcraw_make_mem_image(data, out errc);
// convert pointer to structure to get image info and raw data
var img = PtrToStructure<libraw_processed_image_t>(ptr);
Console.WriteLine("\nImage type: " + img.type);
Console.WriteLine("Image height: " + img.height);
Console.WriteLine("Image width: " + img.width);
Console.WriteLine("Image colors: " + img.colors);
Console.WriteLine("Image bits: " + img.bits);
Console.WriteLine("Data size: " + img.data_size);
Console.WriteLine("Checksum: " + img.height * img.width * img.colors * (img.bits / 8));
// rqeuired step before accessing the "data" array
Array.Resize(ref img.data, (int)img.data_size);
var adr = ptr + OffsetOf(typeof(libraw_processed_image_t), "data").ToInt32();
Copy(adr, img.data, 0, (int)img.data_size);
// calculate padding for lines and add padding
var num = img.width % 4;
var padding = new byte[num];
var stride = img.width * img.colors * (img.bits / 8);
var line = new byte[stride];
var tmp = new List<byte>();
for (var i = 0; i < img.height; i++)
{
Buffer.BlockCopy(img.data, stride * i, line, 0, stride);
tmp.AddRange(line);
tmp.AddRange(padding);
}
// release memory allocated by [libraw_dcraw_make_mem_image]
NativeMethods.libraw_dcraw_clear_mem(ptr);
// create/save bitmap from mem image/array
var bmp = new Bitmap(img.width, img.height, PixelFormat.Format24bppRgb);
var bmd = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadWrite, bmp.PixelFormat);
Copy(tmp.ToArray(), 0, bmd.Scan0, (int)img.data_size);
NativeMethods.libraw_close(data);
var outJPEG = path.Replace(Path.GetExtension(path), ".jpg");
Console.WriteLine("Saving image to: " + outJPEG);
bmp.Save(outJPEG, ImageFormat.Jpeg);
CurrentFilePath = outJPEG;
Bitmaps use a stride to describe the number of bytes of a row, this is to ensure all rows are aligned properly, i.e. stride >= width * bitsPerPixel. Additionally you use a parallel loop over all pixels and this is to fine grained parallelism, you should go parallel over rows, and process each row sequentially.
So your code should look something like
var result = new int[height, width];
Parallel.For(0, height, y =>
{
var rowStart = y * data.Stride;
for(var x = 0 ; x < width; x++){
var i = rowStart + x*3;
var b = bytesArray[i];
var g = bytesArray[i + 1];
var r = bytesArray[i + 2];
result[y, x] = (int) (0.299 * r + 0.587 * g + 0.114 * b);
}
}
);
You might also consider using unsafe code to access the pixelvalues from the datapointer directly, instead of using a large temporary buffer that is allocated on the large object heap and immediately thrown away.
You should probably also do a check of the pixel format to ensure it is bgr24, since your code will fail otherwise.
Node that multidimensional arrays may not be ideal for images. Images are usually indexed like [x,y] and converted to a linear index like var i = y * width + x. For multidimensional arrays the storage order is inverse, this might require indexing like [y,x], or doing unnecessary transpositions of images instead of a straight memory-copy whenever converting data to some other format. So I usually prefer to write my own 2D array since this usually make interoperability much easier:
public class Array2D<T> : IReadOnlyList<T>
{
public int Width { get; }
public int Height { get; }
public T[] Data { get; }
public Array2D(int width, int height)
{
Width = width;
Height = height;
Data = new T[width * height];
}
/// <summary>
/// Index operator
/// Note that this does not check if x is valid, it may wrap around to the next row
/// </summary>
public T this[int x, int y]
{
get => Data[y * Width + x];
set => Data[y * Width + x] = value;
}
}
I am using mvfox-igc camera sdk for getting images from that camera my problem is that i have byte array of mono8 image and i want to convert that pixel data into 24bit image my programming language
can anybody help me please my code looks like this but is is not doing its work properly
processedBitmap = new Bitmap(data.request.imageWidth.read(),data.request.imageHeight.read(),PixelFormat.Format24bppRgb);
unsafe
{
BitmapData bitmapData = processedBitmap.LockBits(new Rectangle(0, 0, processedBitmap.Width, processedBitmap.Height), ImageLockMode.ReadWrite, processedBitmap.PixelFormat);
int bytesPerPixel = System.Drawing.Bitmap.GetPixelFormatSize(processedBitmap.PixelFormat) / 8;
int heightInPixels = bitmapData.Height;
int widthInBytes = bitmapData.Width * bytesPerPixel;
byte* PtrFirstPixel = (byte*)bitmapData.Scan0;
byte* firstby = (byte*)data.request.imageData.read();
int indexx=0;
Parallel.For(0, heightInPixels, y =>
{
byte* currentLine = PtrFirstPixel + (y * bitmapData.Stride);
for (int x = 0; x < widthInBytes; x = x + bytesPerPixel)
{
currentLine[x] = firstby[indexx];//((byte*)data.request.imageData.read())[0];//(byte)oldBlue;
currentLine[x + 1] = firstby[indexx];//((byte*)data.request.imageData.read())[0];//(byte)oldGreen;
currentLine[x + 2] = firstby[indexx];//((byte*)data.request.imageData.read())[0];//(byte)oldRed;
indexx = indexx + 1;
}
});
processedBitmap.UnlockBits(bitmapData);
processedBitmap.Save("bmp.jpg");
I am not very familiar with bitmaps and I need to save a FrameworkElement (specificaly Grid) as bitmap and copy it to buffer. The problem is I need to save it in Rgba format, not Pgrba, which isn't supported in RenderTargetBitmap. Relevant code is here:
_targetBitmap = new RenderTargetBitmap(xres, yres, 96, 96, PixelFormats.Pbgra32);
_targetBitmap.Clear();
// Child is grid
_targetBitmap.Render(Child);
// copy the pixels into the buffer
_targetBitmap.CopyPixels(new Int32Rect(0, 0, xres, yres), bufferPtr, _bufferSize, _stride);
I tried using WriteableBitmap, but I didn't how to render the Child. Any suggestions?
The CopyPixels function is already giving you direct access to the pixel data, so all you need to do is convert between formats. In this case, you need to swap the channel orders around and undo the premultiplication of alpha values.
NOTE: This code assumes your bufferPtr is a byte array or a byte pointer.
for (int y = 0; y < yres; y++)
{
for (int x = 0; x < xres; x++)
{
// Calculate array offset for this pixel
int offset = y * _stride + x * 4;
// Extract individual color channels from pixel value
int pb = bufferPtr[offset];
int pg = bufferPtr[offset + 1];
int pr = bufferPtr[offset + 2];
int alpha = bufferPtr[offset + 3];
// Remove premultiplication
int r = 0, g = 0, b = 0;
if (alpha > 0)
{
r = pr * 255 / alpha;
g = pg * 255 / alpha;
b = pb * 255 / alpha;
}
// Write color channels in desired order
bufferPtr[offset] = (byte)r;
bufferPtr[offset + 1] = (byte)g;
bufferPtr[offset + 2] = (byte)b;
bufferPtr[offset + 3] = (byte)alpha;
}
}
What is the WPF equivalent of the following Java SWT code? I want to create an Image from a list of RGBA values and display on a Canvas.
private Image GetImage()
{
ImageData imageData = new ImageData(imageWidth, imageHeight,32,palette);
int pixelVecLoc=0;
for (int h = 0; h<imageHeight && (pixelVecLoc < currentImagePixelVec.size()); h++)
{
for (int w = 0; w<imageWidth && (pixelVecLoc < currentImagePixelVec.size()); w++)
{
int p = 0;
Pixel pixel = currentImagePixelVec.get(pixelVecLoc);
p = (pixel.Alpha<<24) | (pixel.Red<<16) | (pixel.Green<<8) | pixel.Blue;
imageData.setPixel(w, h, p);
pixelVecLoc++;
}
}
imageData = imageData.scaledTo(imageScaleWidth, imageScaleHeight);
Image image = ImageDescriptor.createFromImageData(imageData).createImage();
return image;
}
Then draw it on a Canvas:
gc.drawImage(image, 0, 0);
This is a short snippet showing how you can create a custom RGBA buffer and write pixel data to it (based on this example):
int width = 512;
int height = 256;
int stride = width * 4 + (width % 4);
int pixelWidth = 4; // RGBA (BGRA)
byte[] imageData = new byte[width * stride]; // raw byte buffer
for (int y = 0; y < height; y++)
{
int yPos = y * stride;
for (int x = 0; x < width; x++)
{
int xPos = yPos + x * pixelWidth;
imageData[xPos + 2] = (byte) (RedValue); // replace *Value with source data
imageData[xPos + 1] = (byte) (GreenValue);
imageData[xPos ] = (byte) (BlueValue);
imageData[xPos + 3] = (byte) (AlphaValue);
}
}
Then use the BitmapSource.Create Method (Int32, Int32, Double, Double, PixelFormat, BitmapPalette, IntPtr, Int32, Int32) method together with a PixelFormats:
BitmapSource bmp =
BitmapSource.Create(
width,
height,
96, // Horizontal DPI
96, // Vertical DPI
PixelFormats.Bgra32, // 32-bit BGRA
null, // no palette
imageData, // byte buffer
imageData.Length, // buffer size
stride); // stride
Note that the byte-order is reverse except the alpha component (BGRA) as shown in the snippet.
To transfer the result to canvas you can first create an Image, set the BitmapSource as Source and finally add that to the canvas:
// create image and set image as source
Image BmpImg = New Image();
BmpImg.Width = width;
BmpImg.Height = height;
BmpImg.Source = bmp;
// add image to canvas
canvas.Children.Add(BmpImg);
Canvas.SetLeft(BmpImg, 0); // to set position (x,y)
Canvas.SetTop(BmpImg, 0);
I have as input a Ushort array of image data. Other inputs are gathered here, such as the 'Width, Height'. The ushort array also carries a Min and Max values, that I want to use, those are stored in 'io_current'.
I want to return a Format8ppIndexed Bitmap, and I have this code but what am I doing wrong?:
private Bitmap CreateBitmap(ushort[,] pixels16)
{
int width = pixels16.GetLength(1);
int height = pixels16.GetLength(0);
Bitmap bmp = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format8bppIndexed);
BitmapData bmd = bmp.LockBits(new Rectangle(0, 0, width, height),
System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);
// This 'unsafe' part of the code populates the bitmap bmp with data stored in pixel16.
// It does so using pointers, and therefore the need for 'unsafe'.
unsafe
{
//int pixelSize = 4;
int i, j; //, j1; //, i1;
byte b;
ushort sVal;
double lPixval;
//The array has max and min constraints
int distance = io_current.MaximumValue - io_current.MinimumValue;
for (i = 0; i < bmd.Height; ++i)
{
byte* row = (byte*)bmd.Scan0 + (i * bmd.Stride);
//i1 = i * bmd.Height;
for (j = 0; j < bmd.Width; ++j)
{
sVal = (ushort)(pixels16[i, j]);
lPixval = ((sVal - io_current.MinimumValue) * 255) / distance; // Convert to a 255 value range
//lPixval = ((sVal - io_current.MinimumValue) / distance) * 255;
//lPixval = 255 - lPixval; //invert the value
if (lPixval > 255) lPixval = 255;
if (lPixval < 0) lPixval = 0;
b = (byte)(lPixval);
//j1 = j * pixelSize; //Pixelsize is one
row[j] = b; // Just one in 8bpp
//Not necessary for format8bppindexed
//row[j1] = b; // Red
//row[j1 + 1] = b; // Green
//row[j1 + 2] = b; // Blue
//row[j1 + 3] = 255; //No Alpha channel in 24bit
}
}
}
bmp.UnlockBits(bmd);
return bmp;
}
I'm getting a Black screen, or a one color screen. Basically no usable data is returned. Obviously from the comments. I tried to convert this from 24bit bitmap code and thought it would be easy.