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;
}
}
Related
I have a problem. I need to perform this function with lockbits. Please I need help.
public void xPix(Bitmap bmp, int n, Color cx, Color nx)
{
try
{
for (int y = 0; y < bmp.Height; y++)
{
for (int x = 0; x < bmp.Width; x += (n * 2))
{
cx = bmp.GetPixel(x, y);
if (x + n <= bmp.Width - 1) nx = bmp.GetPixel(x + n, y);
bmp.SetPixel(x, y, nx);
if (x + n <= bmp.Width - 1) bmp.SetPixel(x + n, y, cx);
}
}
}
catch { }
}
There were lots of things that didn't make sense to me about your code. I fixed the pieces that were preventing an image from appearing and here is the result. I will explain my changes after the code.
public void xPix(Bitmap bmp, int n, Color cx, Color nx)
{
var img = bmp.LockBits(new Rectangle(Point.Empty, bmp.Size), System.Drawing.Imaging.ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
byte[] bmpBytes = new byte[Math.Abs(img.Stride) * img.Height];
System.Runtime.InteropServices.Marshal.Copy(img.Scan0, bmpBytes, 0, bmpBytes.Length);
for (int y = 0; y < img.Height; y++)
{
for (int x = 0; x < img.Width; x+=n*2)
{
cx = Color.FromArgb(BitConverter.ToInt32(bmpBytes, y * Math.Abs(img.Stride) + x * 4));
if (x + n <= img.Width - 1) nx = Color.FromArgb(BitConverter.ToInt32(bmpBytes, y * Math.Abs(img.Stride) + x * 4));
BitConverter.GetBytes(nx.ToArgb()).CopyTo(bmpBytes, y * Math.Abs(img.Stride) + x * 4);
if (x + n <= img.Width - 1) BitConverter.GetBytes(cx.ToArgb()).CopyTo(bmpBytes, y * Math.Abs(img.Stride) + (x + n) * 4);
}
}
System.Runtime.InteropServices.Marshal.Copy(bmpBytes, 0, img.Scan0, bmpBytes.Length);
bmp.UnlockBits(img);
}
protected override void OnClick(EventArgs e)
{
base.OnClick(e);
Bitmap bmp = new Bitmap(#"C:\Users\bluem\Downloads\Default.png");
for (int i = 0; i < bmp.Width; i++)
{
xPix(bmp, new Random().Next(20) + 1, System.Drawing.Color.White, System.Drawing.Color.Green);
}
Canvas.Image = bmp;
}
There's no such class as LockBitmap so I replaced it with the result of a call to Bitmap.LockBits directly.
The result of LockBits does not include functions for GetPixel and SetPixel, so I did what one normally does with the result of LockBits (see https://learn.microsoft.com/en-us/dotnet/api/system.drawing.bitmap.lockbits?view=netframework-4.7.2) and copied the data into a byte array instead.
When accessing the byte data directly, some math must be done to convert the x and y coordinates into a 1-dimensional coordinate within the array of bytes, which I did.
When accessing the byte data directly under the System.Drawing.Imaging.PixelFormat.Format32bppArgb pixel format, multiple bytes must be accessed to convert between byte data and a pixel color, which I did with BitConverter.GetBytes, BitConverter.ToInt32, Color.FromArgb and Color.ToArgb.
I don't think it's a good idea to be changing the Image in the middle of painting it. You should either be drawing the image directly during the Paint event, or changing the image outside the Paint event and allowing the system to draw it. So I used the OnClick of my form to trigger the function instead.
The first random number I got was 0, so I had to add 1 to avoid an endless loop.
The cx and nx parameters never seem to be used as inputs, so I put arbitrary color values in for them. Your x and y variables were not defined/declared anywhere.
If you want faster on-image-action, you can use Marshall.Copy method with Parallel.For
Why dont use GetPixel method? Because every time you call it, your ALL image is loaded to memory. GetPixel get one pixel, and UNLOAD all image. And in every iteration, ALL image is loaded to memory (for example, if u r working on 500x500 pix image, GetPixel will load 500x500 times whole pixels to memory). When you work on images with C# (CV stuff), work on raw bytes from memory.
I will show how to use with Lockbits in Binarization because its easy to explain.
int pixelBPP = Image.GetPixelFormatSize(resultBmp.PixelFormat) / 8;
unsafe
{
BitmapData bmpData = resultBmp.LockBits(new Rectangle(0, 0, resultBmp.Width, resultBmp.Height), ImageLockMode.ReadWrite, resultBmp.PixelFormat);
byte* ptr = (byte*)bmpData.Scan0; //addres of first line
int height = resultBmp.Height;
int width = resultBmp.Width * pixelBPP;
Parallel.For(0, height, y =>
{
byte* offset = ptr + (y * bmpData.Stride); //set row
for(int x = 0; x < width; x = x + pixelBPP)
{
byte value = (offset[x] + offset[x + 1] + offset[x + 2]) / 3 > threshold ? Byte.MaxValue : Byte.MinValue;
offset[x] = value;
offset[x + 1] = value;
offset[x + 2] = value;
if (pixelBPP == 4)
{
offset[x + 3] = 255;
}
}
});
resultBmp.UnlockBits(bmpData);
}
Now, example with Marshall.copy:
BitmapData bmpData = resultBmp.LockBits(new Rectangle(0, 0, resultBmp.Width, resultBmp.Height),
ImageLockMode.ReadWrite,
resultBmp.PixelFormat
);
int bytes = bmpData.Stride * resultBmp.Height;
byte[] pixels = new byte[bytes];
Marshal.Copy(bmpData.Scan0, pixels, 0, bytes); //loading bytes to memory
int height = resultBmp.Height;
int width = resultBmp.Width;
Parallel.For(0, height - 1, y => //seting 2s and 3s
{
int offset = y * stride; //row
for (int x = 0; x < width - 1; x++)
{
int positionOfPixel = x + offset + pixelFormat; //remember about pixel format!
//do what you want with pixel
}
}
});
Marshal.Copy(pixels, 0, bmpData.Scan0, bytes); //copying bytes to bitmap
resultBmp.UnlockBits(bmpData);
Remember, when you warking with RAW bytes very important is to remember about PixelFormat. If you work on RGBA image, you need to set up every channel. (for example offset + x + pixelFormat). I showed it in Binarization example, how to deak with RGBA image with raw data. If lockbits are not fast enough, use Marshall.Copy
I am writing a .Net wrapper for Tesseract Ocr and if I use a grayscale image instead of rgb image as an input file to it then results are pretty good.
So I was searching the web for C# solution to convert a Rgb image to grayscale image and I found this code.
This performs 3 operations to increase the accuracy of tesseract.
Resize the image
then convert into grayscale image and remove noise from image
Now this converted image gives almost 90% accurate results.
//Resize
public Bitmap Resize(Bitmap bmp, int newWidth, int newHeight)
{
Bitmap temp = (Bitmap)bmp;
Bitmap bmap = new Bitmap(newWidth, newHeight, temp.PixelFormat);
double nWidthFactor = (double)temp.Width / (double)newWidth;
double nHeightFactor = (double)temp.Height / (double)newHeight;
double fx, fy, nx, ny;
int cx, cy, fr_x, fr_y;
Color color1 = new Color();
Color color2 = new Color();
Color color3 = new Color();
Color color4 = new Color();
byte nRed, nGreen, nBlue;
byte bp1, bp2;
for (int x = 0; x < bmap.Width; ++x)
{
for (int y = 0; y < bmap.Height; ++y)
{
fr_x = (int)Math.Floor(x * nWidthFactor);
fr_y = (int)Math.Floor(y * nHeightFactor);
cx = fr_x + 1;
if (cx >= temp.Width)
cx = fr_x;
cy = fr_y + 1;
if (cy >= temp.Height)
cy = fr_y;
fx = x * nWidthFactor - fr_x;
fy = y * nHeightFactor - fr_y;
nx = 1.0 - fx;
ny = 1.0 - fy;
color1 = temp.GetPixel(fr_x, fr_y);
color2 = temp.GetPixel(cx, fr_y);
color3 = temp.GetPixel(fr_x, cy);
color4 = temp.GetPixel(cx, cy);
// Blue
bp1 = (byte)(nx * color1.B + fx * color2.B);
bp2 = (byte)(nx * color3.B + fx * color4.B);
nBlue = (byte)(ny * (double)(bp1) + fy * (double)(bp2));
// Green
bp1 = (byte)(nx * color1.G + fx * color2.G);
bp2 = (byte)(nx * color3.G + fx * color4.G);
nGreen = (byte)(ny * (double)(bp1) + fy * (double)(bp2));
// Red
bp1 = (byte)(nx * color1.R + fx * color2.R);
bp2 = (byte)(nx * color3.R + fx * color4.R);
nRed = (byte)(ny * (double)(bp1) + fy * (double)(bp2));
bmap.SetPixel(x, y, System.Drawing.Color.FromArgb(255, nRed, nGreen, nBlue));
}
}
//here i included the below to functions logic without the for loop to remove repetitive use of for loop but it did not work and taking the same time.
bmap = SetGrayscale(bmap);
bmap = RemoveNoise(bmap);
return bmap;
}
//SetGrayscale
public Bitmap SetGrayscale(Bitmap img)
{
Bitmap temp = (Bitmap)img;
Bitmap bmap = (Bitmap)temp.Clone();
Color c;
for (int i = 0; i < bmap.Width; i++)
{
for (int j = 0; j < bmap.Height; j++)
{
c = bmap.GetPixel(i, j);
byte gray = (byte)(.299 * c.R + .587 * c.G + .114 * c.B);
bmap.SetPixel(i, j, Color.FromArgb(gray, gray, gray));
}
}
return (Bitmap)bmap.Clone();
}
//RemoveNoise
public Bitmap RemoveNoise(Bitmap bmap)
{
for (var x = 0; x < bmap.Width; x++)
{
for (var y = 0; y < bmap.Height; y++)
{
var pixel = bmap.GetPixel(x, y);
if (pixel.R < 162 && pixel.G < 162 && pixel.B < 162)
bmap.SetPixel(x, y, Color.Black);
}
}
for (var x = 0; x < bmap.Width; x++)
{
for (var y = 0; y < bmap.Height; y++)
{
var pixel = bmap.GetPixel(x, y);
if (pixel.R > 162 && pixel.G > 162 && pixel.B > 162)
bmap.SetPixel(x, y, Color.White);
}
}
return bmap;
}
But the problem is it takes lot of time to convert it
So I included SetGrayscale(Bitmap bmap)
RemoveNoise(Bitmap bmap) function logic inside the Resize() method to remove repetitive use of for loop
but it did not solve my problem.
The Bitmap class's GetPixel() and SetPixel() methods are notoriously slow for multiple read/writes. A much faster way to access and set individual pixels in a bitmap is to lock it first.
There's a good example here on how to do that, with a nice class LockedBitmap to wrap around the stranger Marshaling code.
Essentially what it does is use the LockBits() method in the Bitmap class, passing a rectangle for the region of the bitmap you want to lock, and then copy those pixels from its unmanaged memory location to a managed one for easier access.
Here's an example on how you would use that example class with your SetGrayscale() method:
public Bitmap SetGrayscale(Bitmap img)
{
LockedBitmap lockedBmp = new LockedBitmap(img.Clone());
lockedBmp.LockBits(); // lock the bits for faster access
Color c;
for (int i = 0; i < lockedBmp.Width; i++)
{
for (int j = 0; j < lockedBmp.Height; j++)
{
c = lockedBmp.GetPixel(i, j);
byte gray = (byte)(.299 * c.R + .587 * c.G + .114 * c.B);
lockedBmp.SetPixel(i, j, Color.FromArgb(gray, gray, gray));
}
}
lockedBmp.UnlockBits(); // remember to release resources
return lockedBmp.Bitmap; // return the bitmap (you don't need to clone it again, that's already been done).
}
This wrapper class has saved me a ridiculous amount of time in bitmap processing. Once you've implemented this in all your methods, preferably only calling LockBits() once, then I'm sure your application's performance will improve tremendously.
I also see that you're cloning the images a lot. This probably doesn't take up as much time as the SetPixel()/GetPixel() thing, but its time can still be significant especially with larger images.
The easiest way would be to redraw the image onto itself using DrawImage and passing a suitable ColorMatrix. Google for ColorMatrix and gray scale and you'll find a ton of examples, this one for example: http://www.codeproject.com/Articles/3772/ColorMatrix-Basics-Simple-Image-Color-Adjustment
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.
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