Black border around circle after copying bitmap to another bitmap - c#

I have bitmap extracted from BitmapSource (RenderTargetBitmap) with blue circle in it. RenderTargetBitmap is created with PixelFormats.Pbgra32.
PixelFormats Pbgra32 pre-multiplies each color channel with alpha value. So, when I try to convert bitmap to cursor I was getting less opaque image than is should have.
I found solution to the problem here which clone the bitmap to Format24bppRgb and manually set R,B,G and alpha values. However, solutions works perfectly fine but for cloned bitmap I see black border around visual.
Can I get rid of that black border in cloned bitmap? (I suspect it's something inside SafeCopy method)
Methods used from the link are:
private static void SafeCopy(BitmapData srcData, BitmapData dstData, byte alphaLevel)
{
for (int y = 0; y < srcData.Height; y++)
for (int x = 0; x < srcData.Width; x++)
{
byte b = Marshal.ReadByte(srcData.Scan0, y * srcData.Stride + x * 3);
byte g = Marshal.ReadByte(srcData.Scan0, y * srcData.Stride + x * 3 + 1);
byte r = Marshal.ReadByte(srcData.Scan0, y * srcData.Stride + x * 3 + 2);
Marshal.WriteByte(dstData.Scan0, y * dstData.Stride + x * 4, b);
Marshal.WriteByte(dstData.Scan0, y * dstData.Stride + x * 4 + 1, g);
Marshal.WriteByte(dstData.Scan0, y * dstData.Stride + x * 4 + 2, r);
Marshal.WriteByte(dstData.Scan0, y * dstData.Stride + x * 4 + 3, alphaLevel);
}
}
private static Cursor CreateCustomCursorInternal(Bitmap bitmap, double opacity)
{
Bitmap cursorBitmap = null;
IconInfo iconInfo = new IconInfo();
Rectangle rectangle = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
try
{
byte alphaLevel = System.Convert.ToByte(byte.MaxValue * opacity);
// Here, the pre-multiplied alpha channel is specified
cursorBitmap = new Bitmap(bitmap.Width, bitmap.Height,
PixelFormat.Format32bppPArgb);
// Assuming the source bitmap can be locked in a 24 bits per pixel format
BitmapData bitmapData = bitmap.LockBits(rectangle, ImageLockMode.ReadOnly,
PixelFormat.Format24bppRgb);
BitmapData cursorBitmapData = cursorBitmap.LockBits(rectangle,
ImageLockMode.WriteOnly, cursorBitmap.PixelFormat);
// Use SafeCopy() to set the bitmap contents
SafeCopy(bitmapData, cursorBitmapData, alphaLevel);
cursorBitmap.UnlockBits(cursorBitmapData);
bitmap.UnlockBits(bitmapData);
.......
}
Original bitmap:
Cloned bitmap:

The simplest way to convert a WPF 32bit PBGRA bitmap to a WinForms PARGB bitmap and at the same time apply a global opacity seems to be just multiplying all A, R, G and B values with the opacity factor (a float value between 0 and 1) like in the method shown below. However, I would have expected that it would also be necessary to swap the bytes, but apparently it isn't.
private static void CopyBufferWithOpacity(byte[] sourceBuffer,
System.Drawing.Imaging.BitmapData targetBuffer, double opacity)
{
for (int i = 0; i < sourceBuffer.Length; i++)
{
sourceBuffer[i] = (byte)Math.Round(opacity * sourceBuffer[i]);
}
Marshal.Copy(sourceBuffer, 0, targetBuffer.Scan0, sourceBuffer.Length);
}
Given a 32bit PBGRA bitmap pbgraBitmap (e.g. a RenderTargetBitmap), you would use the method like this:
var width = pbgraBitmap.PixelWidth;
var height = pbgraBitmap.PixelHeight;
var stride = width * 4;
var buffer = new byte[stride * height];
pbgraBitmap.CopyPixels(buffer, stride, 0);
var targetFormat = System.Drawing.Imaging.PixelFormat.Format32bppPArgb;
var bitmap = new System.Drawing.Bitmap(width, height, targetFormat);
var bitmapData = bitmap.LockBits(
new System.Drawing.Rectangle(0, 0, width, height),
System.Drawing.Imaging.ImageLockMode.WriteOnly,
targetFormat);
CopyBufferWithOpacity(buffer, bitmapData, 0.6);
bitmap.UnlockBits(bitmapData);

Related

Resizing a Bitmap manually

We are using a camera that acquires up to 60 frames per second, providing Bitmaps for us to use in our codebase.
As our wpf-app requires, these bitmaps are scaled based on a scaling factor; That scaling-process is by far the most limiting factor when it comes to actually displaying 60 fps. I am aware of new Bitmap(Bitmap source, int width, int height) which is obviously the simplest way to resize a Bitmap;
Nevertheless, I am trying to implement a "manual" approach using BitmapData and pointers. I have come up with the following:
public static Bitmap /*myMoBetta*/ResizeBitmap(this Bitmap bmp, double scaleFactor)
{
int desiredWidth = (int)(bmp.Width * scaleFactor),
desiredHeight = (int)(bmp.Height * scaleFactor);
var scaled = new Bitmap(desiredWidth, desiredHeight, bmp.PixelFormat);
int formatSize = (int)Math.Ceiling(Image.GetPixelFormatSize(bmp.PixelFormat)/8.0);
BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, bmp.PixelFormat);
BitmapData scaledData = scaled.LockBits(new Rectangle(0, 0, scaled.Width, scaled.Height), ImageLockMode.WriteOnly, scaled.PixelFormat);
unsafe
{
var srcPtr = (byte*)bmpData.Scan0.ToPointer();
var destPtr = (byte*)scaledData.Scan0.ToPointer();
int scaledDataSize = scaledData.Stride * scaledData.Height;
int nextPixel = (int)(1 / scaleFactor)*formatSize;
Parallel.For(0, scaledDataSize - formatSize,
i =>
{
for (int j = 0; j < formatSize; j++)
{
destPtr[i + j] = srcPtr[i * nextPixel + j];
}
});
}
bmp.UnlockBits(bmpData);
bmp.Dispose();
scaled.UnlockBits(scaledData);
return scaled;
}
Given scalingFactor < 1.
Actually using this algorithm does not seem to work, though. How are the bits of each pixel arranged in memory, exactly? My guess was that calling Image.GetPixelFormatSize() and deviding its result by 8 returns the number of bytes per pixel; But continuing to copy only formatSize amout of bytes every 1 / scaleFactor * formatSize byte results in a corrupted image.
What am I missing?
After some more research I bumped into OpenCV that has it's own .NET implementation with Emgu.CV, containing relevant methods for faster resizing.
My ResizeBitmap()-function has shrinked significantly:
public static Bitmap ResizeBitmap(this Bitmap bmp, int width, int height)
{
var desiredSize = new Size(width, height);
var src = new Emgu.CV.Image<Rgb, byte>(bmp);
var dest = new Emgu.CV.Image<Rgb, byte>(desiredSize);
Emgu.CV.CvInvoke.Resize(src, dest, desiredSize);
bmp.Dispose();
src.Dispose();
return dest.ToBitmap();
}
I have not tested performance thouroughly, but while debugging, this implementation reduced executiontime from 22ms with new Bitmap(source, width, height) to about 7ms.

How to do Bitmap processing using BitmapData?

I've built a small test example where the goal is to change all pixels in my .png to white. I'm doing it using BitmapData, because as I understand it, the performance is better. If I can get it working; then I can change which pixels I'm changing and add different conditions to altering a pixel color. But I'm stuck on just this simple test.
Here's my C# :
public static void TestConvertAllBlackBitmapToAllWhite()
{
string allBlackPNGFullFilePath = #"C:\Users\{Username}\Desktop\50x50AllBlack.png";
Bitmap allBlackBitmap = new Bitmap(allBlackPNGFullFilePath);
Bitmap newBitmap = (Bitmap)allBlackBitmap.Clone();
Size size = newBitmap.Size;
PixelFormat pixelFormat = newBitmap.PixelFormat;
byte bitDepth = (byte)(pixelFormat == PixelFormat.Format32bppArgb ? 4 : 3);
Rectangle rectangle = new Rectangle(Point.Empty, size);
BitmapData bitmapData = newBitmap.LockBits(rectangle, ImageLockMode.ReadOnly, pixelFormat);
int dataSize = bitmapData.Stride * bitmapData.Height;
byte[] data = new byte[dataSize];
Marshal.Copy(bitmapData.Scan0, data, 0, dataSize);
Color white = Color.White;
for (int y = 0; y < size.Height; y++)
{
for (int x = 0; x < size.Width; x++)
{
// Get Index
int index = y * bitmapData.Stride + x * bitDepth;
// Set Pixel Color
data[index] = white.B;
data[index + 1] = white.G;
data[index + 2] = white.R;
}
}
Marshal.Copy(data, 0, bitmapData.Scan0, data.Length);
newBitmap.UnlockBits(bitmapData);
// Save New Converted Bitmap
string originalFileName = Path.GetFileNameWithoutExtension(allBlackPNGFullFilePath);
string directory = Path.GetDirectoryName(allBlackPNGFullFilePath);
string newBitmapFileName = originalFileName + "_Converted";
string newBitmapFullFileName = directory + Path.DirectorySeparatorChar.ToString() + newBitmapFileName + ".png";
newBitmap.Save(newBitmapFullFileName, ImageFormat.Png);
}
My input is an all black 50x50 .png :
The problem is the output I'm getting is another all black .png instead of an all white one.
How can I fix up my simple example code to produce an all white .png as a result?
Any help / guidance will be really appreciated.
As pointed out by #Taw
It's a little thing on this line :
BitmapData bitmapData = newBitmap.LockBits(rectangle, ImageLockMode.ReadOnly, pixelFormat);
The ImageLockMode is set to ReadOnly. Since I'm making changes to the BitmapData while looping; the ImageLockMode should be ReadWrite

Convert 32-bit Bitmap to 8-bit (both color and grayscale)

I have a System.Drawing.Bitmap with PixelFormat of Format32bppRgb.
I want this image to be converted to a bitmap of 8bit.
The following is the code to convert a 32-bit image to an 8-bit grayscale image:
public static Bitmap ToGrayscale(Bitmap bmp)
{
int rgb;
System.Drawing.Color c;
for (int y = 0; y < bmp.Height; y++)
for (int x = 0; x < bmp.Width; x++)
{
c = bmp.GetPixel(x, y);
rgb = (int)((c.R + c.G + c.B) / 3);
bmp.SetPixel(x, y, System.Drawing.Color.FromArgb(rgb, rgb, rgb));
}
return bmp;
}
However, the Bitmap I end up with still has the PixelFormat Property of Format32bppRgb.
Also,
How can I convert a 32-bit color image into an 8-bit color image?
Thanks for any input!
Related.
- Convert RGB image to RGB 16-bit and 8-bit
- C# - How to convert an Image into an 8-bit color Image?
- C# Convert Bitmap to indexed colour format
- Color Image Quantization in .NET
- quantization (Reduction of colors of image)
- The best way to reduce quantity of colors in bitmap palette
You must create (and return) new instance of Bitmap.
PixelFormat is specified in constructor of Bitmap and can not be changed.
Edit (30 March 2022): fixed byte array access expression from x * data.Stride + y to y * data.Stride + x and changed the palette to be grayscale.
EDIT:
Sample code based on this answer on MSDN:
public static Bitmap ToGrayscale(Bitmap bmp) {
var result = new Bitmap(bmp.Width, bmp.Height, PixelFormat.Format8bppIndexed);
var resultPalette = result.Palette;
for (int i = 0; i < 256; i++)
{
resultPalette.Entries[i] = Color.FromArgb(255, i, i, i);
}
result.Palette = resultPalette;
BitmapData data = result.LockBits(new Rectangle(0, 0, result.Width, result.Height), ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
// Copy the bytes from the image into a byte array
byte[] bytes = new byte[data.Height * data.Stride];
Marshal.Copy(data.Scan0, bytes, 0, bytes.Length);
for (int y = 0; y < bmp.Height; y++) {
for (int x = 0; x < bmp.Width; x++) {
var c = bmp.GetPixel(x, y);
var rgb = (byte)((c.R + c.G + c.B) / 3);
bytes[y * data.Stride + x] = rgb;
}
}
// Copy the bytes from the byte array into the image
Marshal.Copy(bytes, 0, data.Scan0, bytes.Length);
result.UnlockBits(data);
return result;
}

convert 8 bit color bmp image to 8 bit grayscale bmp

This is my bitmap object
Bitmap b = new Bitmap(columns, rows, PixelFormat.Format8bppIndexed);
BitmapData bmd = b.LockBits(new Rectangle(0, 0, columns, rows), ImageLockMode.ReadWrite, b.PixelFormat);
How do i convert this into a 8 bit grayscale bitmap ?
Yes, no need to change the pixels, just the palette is fine. ColorPalette is a flaky type, this sample code worked well:
var bmp = Image.FromFile("c:/temp/8bpp.bmp");
if (bmp.PixelFormat != System.Drawing.Imaging.PixelFormat.Format8bppIndexed) throw new InvalidOperationException();
var newPalette = bmp.Palette;
for (int index = 0; index < bmp.Palette.Entries.Length; ++index) {
var entry = bmp.Palette.Entries[index];
var gray = (int)(0.30 * entry.R + 0.59 * entry.G + 0.11 * entry.B);
newPalette.Entries[index] = Color.FromArgb(gray, gray, gray);
}
bmp.Palette = newPalette; // Yes, assignment to self is intended
if (pictureBox1.Image != null) pictureBox1.Image.Dispose();
pictureBox1.Image = bmp;
I don't actually recommend you use this code, indexed pixel formats are a pita to deal with. You'll find a fast and more general color-to-grayscale conversion in this answer.
Something like:
Bitmap b = new Bitmap(columns, rows, PixelFormat.Format8bppIndexed);
for (int i = 0; i < columns; i++)
{
for (int x = 0; x < rows; x++)
{
Color oc = b.GetPixel(i, x);
int grayScale = (int)((oc.R * 0.3) + (oc.G * 0.59) + (oc.B * 0.11));
Color nc = Color.FromArgb(oc.A, grayScale, grayScale, grayScale);
b.SetPixel(i, x, nc);
}
}
BitmapData bmd = b.LockBits(new Rectangle(0, 0, columns, rows), ImageLockMode.ReadWrite, b.PixelFormat);
hi you could change the color palette to a grayscale one
although the following code is in Vb.net. You could easily convert it to C#
Private Function GetGrayScalePalette() As ColorPalette
Dim bmp As Bitmap = New Bitmap(1, 1, Imaging.PixelFormat.Format8bppIndexed)
Dim monoPalette As ColorPalette = bmp.Palette
Dim entries() As Color = monoPalette.Entries
Dim i As Integer
For i = 0 To 256 - 1 Step i + 1
entries(i) = Color.FromArgb(i, i, i)
Next
Return monoPalette
End Function
Original Source -> http://social.msdn.microsoft.com/Forums/en-us/vblanguage/thread/500f7827-06cf-4646-a4a1-e075c16bbb38
Note that if you want to do the same conversion as modern HDTVs, you'll want to use the Rec. 709 coefficients for the conversion. The ones provided above (.3, .59, .11) are (almost) the Rec. 601 (standard def) coefficients. The Rec. 709 coefficients are gray = 0.2126 R' + 0.7152 G' + 0.0722 B', where R', G', and B' are the gamma adjusted red, green, and blue components.
Check this link out. We did this at university and it works.
It is all you need with input and output.

Improving Image compositing Algorithm c# .NET

I was wondering if anyone could shed some light on improvements I can do in making this compositing algorithm faster. What is does is takes 3 images splits them up to get the 1st Images Red Channel, 2nd Images Green channel and the 3rd Images Blue channel and composites them together into 1 new image. Now it works but at an excruciatingly slow pace. The reason i think down to the pixel by pixel processing it has to do on all image components.
The process is to :
For all images:
Extract respective R G and B values -> composite into 1 image -> Save new Image.
foreach (Image[] QRE2ImgComp in QRE2IMGArray)
{
Globals.updProgress = "Processing frames: " + k + " of " + QRE2IMGArray.Count + " frames done.";
QRMProgressUpd(EventArgs.Empty);
Image RedLayer = GetRedImage(QRE2ImgComp[0]);
QRE2ImgComp[0] = RedLayer;
Image GreenLayer = GetGreenImage(QRE2ImgComp[1]);
QRE2ImgComp[1] = GreenLayer;
Image BlueLayer = GetBlueImage(QRE2ImgComp[2]);
QRE2ImgComp[2] = BlueLayer;
Bitmap composite = new Bitmap(QRE2ImgComp[0].Height, QRE2ImgComp[0].Width);
Color Rlayer,Glayer,Blayer;
byte R, G, B;
for (int y = 0; y < composite.Height; y++)
{
for (int x = 0; x < composite.Width; x++)
{
//pixelColorAlpha = composite.GetPixel(x, y);
Bitmap Rcomp = new Bitmap(QRE2ImgComp[0]);
Bitmap Gcomp = new Bitmap(QRE2ImgComp[1]);
Bitmap Bcomp = new Bitmap(QRE2ImgComp[2]);
Rlayer = Rcomp.GetPixel(x, y);
Glayer = Gcomp.GetPixel(x, y);
Blayer = Bcomp.GetPixel(x, y);
R = (byte)(Rlayer.R);
G = (byte)(Glayer.G);
B = (byte)(Blayer.B);
composite.SetPixel(x, y, Color.FromArgb((int)R, (int)G, (int)B));
}
}
Globals.updProgress = "Saving frame...";
QRMProgressUpd(EventArgs.Empty);
Image tosave = composite;
Globals.QRFrame = tosave;
tosave.Save("C:\\QRItest\\E" + k + ".png", ImageFormat.Png);
k++;
}
For reference here is the red channel filter method relatively the same for blue and green:
public Image GetRedImage(Image sourceImage)
{
Bitmap bmp = new Bitmap(sourceImage);
Bitmap redBmp = new Bitmap(sourceImage.Width, sourceImage.Height);
for (int x = 0; x < bmp.Width; x++)
{
for (int y = 0; y < bmp.Height; y++)
{
Color pxl = bmp.GetPixel(x, y);
Color redPxl = Color.FromArgb((int)pxl.R, 0, 0);
redBmp.SetPixel(x, y, redPxl);
}
}
Image tout = (Image)redBmp;
return tout;
}
Move these
Bitmap Rcomp = new Bitmap(QRE2ImgComp[0]);
Bitmap Gcomp = new Bitmap(QRE2ImgComp[1]);
Bitmap Bcomp = new Bitmap(QRE2ImgComp[2]);
outside the for-loops!
Other very important points:
avoid using GetPixel - it is VERY SLOW!
Checkout LockBits etc. - this is how pixel-level access is usually done in .NET
Consider using a 3rd-party library (free or commercial)... several have some optimized method built-in to do what you are trying to achieve...
I totally agree with the points Yahia listed in his answer to improve performance. I'd like to add one more point regarding performance. You could use the Parallel class of the .Net Framework to parallelize the execution of your for loops. The following example makes use of the LockBits method and the Parallel class to improve performance (assuming 32 bits per pixel (PixelFormat.Format32bppArgb)):
public unsafe static Bitmap GetBlueImagePerf(Image sourceImage)
{
int width = sourceImage.Width;
int height = sourceImage.Height;
Bitmap bmp = new Bitmap(sourceImage);
Bitmap redBmp = new Bitmap(width, height, bmp.PixelFormat);
BitmapData bd = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, PixelFormat.Format32bppRgb);
BitmapData bd2 = redBmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format32bppRgb);
byte* source = (byte*)bd.Scan0.ToPointer();
byte* target = (byte*)bd2.Scan0.ToPointer();
int stride = bd.Stride;
Parallel.For(0, height, (y1) =>
{
byte* s = source + (y1 * stride);
byte* t = target + (y1 * stride);
for (int x = 0; x < width; x++)
{
// use t[1], s[1] to access green channel
// use t[2], s[2] to access red channel
t[0] = s[0];
t += 4; // Add bytes per pixel to current position.
s += 4; // For other pixel formats this value is different.
}
});
bmp.UnlockBits(bd);
redBmp.UnlockBits(bd2);
return redBmp;
}
public unsafe static void DoImageConversion()
{
Bitmap RedLayer = GetRedImagePerf(Image.FromFile("image_path1"));
Bitmap GreenLayer = GetGreenImagePerf(Image.FromFile("image_path2"));
Bitmap BlueLayer = GetBlueImagePerf(Image.FromFile("image_path3"));
Bitmap composite =
new Bitmap(RedLayer.Width, RedLayer.Height, RedLayer.PixelFormat);
BitmapData bd = composite.LockBits(new Rectangle(0, 0, RedLayer.Width, RedLayer.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
byte* comp = (byte*)bd.Scan0.ToPointer();
BitmapData bdRed = RedLayer.LockBits(new Rectangle(0, 0, RedLayer.Width, RedLayer.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
BitmapData bdGreen = GreenLayer.LockBits(new Rectangle(0, 0, RedLayer.Width, RedLayer.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
BitmapData bdBlue = BlueLayer.LockBits(new Rectangle(0, 0, RedLayer.Width, RedLayer.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
byte* red = (byte*)bdRed.Scan0.ToPointer();
byte* green = (byte*)bdGreen.Scan0.ToPointer();
byte* blue = (byte*)bdBlue.Scan0.ToPointer();
int stride = bdRed.Stride;
Parallel.For(0, bdRed.Height, (y1) =>
{
byte* r = red + (y1 * stride);
byte* g = green + (y1 * stride);
byte* b = blue + (y1 * stride);
byte* c = comp + (y1 * stride);
for (int x = 0; x < bdRed.Width; x++)
{
c[0] = b[0];
c[1] = g[1];
c[2] = r[2];
r += 4; // Add bytes per pixel to current position.
g += 4; // For other pixel formats this value is different.
b += 4; // Use Image.GetPixelFormatSize to get number of bits per pixel
c += 4;
}
});
composite.Save("save_image_path", ImageFormat.Jpeg);
}
Hope, this answer gives you a starting point for improving your code.

Categories