In C#, how do I generate a binary mask which has no gray values/noise in the image?
Right now I am able to generate a fairly simple output which looks very close to what I want but has noise around the edges both inside and outside of the white blob (You need to zoom all the way in to see the noise). I am planning to use the image later for image processing but cannot have anything other than black and white values in the image.
Pictures:
Crop 1
Crop 2
Code:
public CropedImage(List<Node> nodes)
{
InitializeComponent();
//List<PointF> listpoints = new List<PointF>();
nodes.ToArray();
PointF[] points = new PointF[nodes.Count];
for(int i = 0; i < nodes.Count; ++i)
{
points[i].X = nodes[i].X;
points[i].Y = nodes[i].Y;
}
Image SourceImage = ImageOperations.GetImage(MainForm.imageMatrix, pictureBox1);
using (Graphics g = Graphics.FromImage(SourceImage))
{
Color black = ColorTranslator.FromHtml("#000000");
Color white = ColorTranslator.FromHtml("#FFFFFF");
using (Brush b = new SolidBrush(black))
{
Rectangle rect = new Rectangle();
g.FillRectangle(b, 0, 0, MainForm.WIDTH, MainForm.HEIGHT);
}
using (Brush b2 = new SolidBrush(white))
{
g.SmoothingMode = SmoothingMode.AntiAlias;
g.FillClosedCurve(b2, points, 0);
}
}
}
How about changing
g.SmoothingMode = SmoothingMode.AntiAlias;
to
g.SmoothingMode = SmoothingMode.None;
If I understand correctly, that would solve your problem.
As eocron said you have to check all the pixels and round them to black or white. the most simple way of doing it (if only you are creating a temp application to do this for a few images) is to use GetPixel() and SetPixel() methods:
Bitmap bmp = Bitmap.FromFile("image.png");
for(int i=0;i<bmp.Width;i++)
for(int j=0;j<bmp.Height;j++)
bmp.SetPixel(i,j, bmp.GetPixel(i,j).R > 127? Color.White: Color.Black); // as its gray I'm only checking Red
However if you are dealing with lots of images especially large ones, or its not a temp application to deal with a few images, and its going to be a feature of your application, the above code is not what you would like to use as it is very slow, and you should use LockBits as it is way faster:
Bitmap bmp = Bitmap.FromFile("image.png");
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
System.Drawing.Imaging.BitmapData bmpData =
bmp.LockBits(rect,System.Drawing.Imaging.ImageLockMode.ReadWrite,bmp.PixelFormat;
IntPtr ptr = bmpData.Scan0;
int bytes = Math.Abs(bmpData.Stride) * bmp.Height;
byte[] rgbValues = new byte[bytes];
System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes);
for (int counter = 0; counter < rgbValues.Length; counter += 4)
{
int c = rgbValues[counter] > 127 ? 255: 0;
rgbValues[counter] = c;
rgbValues[counter+1] = c;
rgbValues[counter+2] = c;
}
System.Runtime.InteropServices.Marshal.Copy(rgbValues, 0, ptr, bytes);
bmp.UnlockBits(bmpData);
Related
I am trying to add a black banner on the top and bottom of an image. I can add the banner but the pixel format of the resulted bitmap is changed to 32-bit. Is there any way to get an 8-bit bitmap as a result.
As mentioned here, If I set the 8-bit pixelFormat in the constructor, creating a graphics will raise an exception.
I read that if I convert from 32 to 8, maybe the pixel values will be different than original ones. Don't know if I can create a new bitmap with the desired height and add the black banners's pixels using for loops. Anyone has a better and simple way?
My code is as below:
Bitmap img = image as Bitmap;
using (Bitmap bitmap = new Bitmap(img.Width, img.Height + 200*2)) // create blank bitmap of desired size
using (Graphics graphics = Graphics.FromImage(bitmap))
{
SolidBrush brush = new SolidBrush(Color.White);
graphics.FillRectangle(brush, 0, 0, img.Width,200);
// draw existing image onto new blank bitmap
graphics.DrawImage(img, 0, 200, img.Width, img.Height);
// draw your rectangle below the original image
graphics.FillRectangle(brush, 0, 200 + img.Height, img.Width, 200);
// bitmap.Save(#"c:\Test1.bmp");
}
Here is a routine that does a FillRectangle on a bmp8bpp bitmap.
You pass in an index which, if positive will be used to put the color into the palette. If you pass in a negative index it will try to find the color and if it doesn't find it it will place the color at the end of the index. This will overwrite whatever color was there before!
void FillIndexedRectangle(Bitmap bmp8bpp, Rectangle rect, Color col, int index)
{
var pal = bmp8bpp.Palette;
int idx = -1;
if (index >= 0) idx = index;
else
{
if (pal.Entries.Where(x => x.ToArgb() == col.ToArgb()).Any())
idx = pal.Entries.ToList().FindIndex(x => x.ToArgb() == col.ToArgb());
if (idx < 0) idx = pal.Entries.Length - 1;
}
pal.Entries[idx] = col;
bmp8bpp.Palette = pal;
var bitmapData =
bmp8bpp.LockBits(new Rectangle(Point.Empty, bmp8bpp.Size),
ImageLockMode.ReadWrite, bmp8bpp.PixelFormat);
byte[] buffer=new byte[bmp8bpp.Width*bmp8bpp.Height];
Marshal.Copy(bitmapData.Scan0, buffer,0, buffer.Length);
for (int y = rect.Y; y < rect.Bottom; y++)
for (int x = rect.X; x < rect.Right; x++)
{
buffer[x + y * bmp8bpp.Width] = (byte)idx;
}
Marshal.Copy(buffer, 0, bitmapData.Scan0,buffer.Length);
bmp8bpp.UnlockBits(bitmapData);
}
Example calls:
Bitmap img = new Bitmap(200, 200, PixelFormat.Format8bppIndexed);
FillIndexedRectangle(img, new Rectangle(0,0,200, 200), Color.Silver, 21);
FillIndexedRectangle(img, new Rectangle(23, 23, 55, 99), Color.Red, 22);
FillIndexedRectangle(img, new Rectangle(123, 123, 55, 33), Color.Black, 23);
FillIndexedRectangle(img, new Rectangle(1, 1, 123, 22), Color.Orange, 34);
FillIndexedRectangle(img, new Rectangle(27, 27, 16, 12), Color.Black, -1);
img.Save("D:\\__bmp8bpp.png");
Result:
There is room for improvement:
All error checking in the lockbits is missing, both wrt pixelformat and rectangle data
Adding colors with a more dynamical scheme could be nice instead of adding to the end
A scheme for finding the closest color already in the palette could also be nice
A scheme for drawing with transparency would also be nice. For this all necessary new colors would have to be determined first; also the tranparency mode.
Maybe one should return the index used from the method so the next calls can refer to it..
For other shapes than rectangles one could use a copy routine that first draws them onto a 'normal' 32bpp bitmap and then transfers the pixels to the buffer..
Update: Here are a few lines to add (**) or change (*) to allow drawing unfilled rectangles; stroke 0 fills the rectangle..:
void FillIndexedRectangle(Bitmap bmp8bpp, Rectangle rect,
Color col, int index, int stroke) // *
...
...
Marshal.Copy(bitmapData.Scan0, buffer,0, buffer.Length);
Rectangle ri = rect; //**
if (stroke > 0) ri.Inflate(-stroke, -stroke); //**
for (int y = rect.Y; y < rect.Bottom; y++)
for (int x = rect.X; x < rect.Right; x++)
{
if (ri == rect || !ri.Contains(x,y)) //**
buffer[x + y * bmp8bpp.Width] = (byte)idx;
}
Marshal.Copy(buffer, 0, bitmapData.Scan0,buffer.Length);
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 have a png image with an alpha channel, for one of our tool which does not support alpha I need to edit the image so that every pixel which is transparent gets a specific color but keeps the alpha so the png still works for all the tools who supports alpha.
Here's my code so far, but it doesn't work, anyone can help? Picture is my source png and I chose red as the color.
Thanks
using (Bitmap ImageAttachedConverted = new Bitmap(Picture.Width, Picture.Height, System.Drawing.Imaging.PixelFormat.Format32bppRgb))
{
using (Graphics GraphicsPicture = Graphics.FromImage(ImageAttachedConverted))
{
GraphicsPicture.InterpolationMode = InterpolationMode.HighQualityBicubic;
GraphicsPicture.CompositingQuality = CompositingQuality.HighQuality;
//GraphicsPicture.Clear(Color.Red);
//GraphicsPicture.DrawImage(
// Picture,
// new Rectangle(0, 0, ImageAttachedConverted.Width, ImageAttachedConverted.Height),
// new Rectangle(0, 0, Picture.Width, Picture.Height),
// GraphicsUnit.Pixel);
// 2nd method pixel bypixel
Bitmap img = Picture as Bitmap;
for (int y = 0; y < ImageAttachedConverted.Height; y++)
{
for (int x = 0; x < ImageAttachedConverted.Width; x++)
{
//Get Colours at the pixel point
Color col1 = img.GetPixel(x, y);
Color temp = Color.FromArgb(
0,
255,
0,
0);
if (col1.A < 100)
ImageAttachedConverted.SetPixel(x, y, temp);
else ImageAttachedConverted.SetPixel(x, y, col1);
}
}
Color temp2 = Color.FromArgb(
0,
255,
0,
0);
//ImageAttachedConverted.MakeTransparent(temp2);
}
ImageAttachedConverted.Save(Destination.FullName, ImageFormat.Png);
}
i am creating png image which painted on my base, from the base i can save a png image, for your reference
Graphics g = e.Graphics;
....
g.DrawLine(pen, new Point(x, y), new Point(x1, y1));
.....
base.OnPaint(e);
using (var bmp = new Bitmap(500, 50))
{
base.DrawToBitmap(bmp, new Rectangle(0, 0, 500, 50));
bmp.Save(outPath);
}
this is single color transparency image, now how do i can inverse this image like png filled with any color and the real image portion should be transparent, is there any possibilities?
bit detail : so transparent will go nontransparent and where there is fill will go to transparent
There's a faster way if you're willing to use unsafe code:
private unsafe void Invert(Bitmap bmp)
{
int w = bmp.Width, h = bmp.Height;
BitmapData data = bmp.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
int* bytes = (int*)data.Scan0;
for ( int i = w*h-1; i >= 0; i-- )
bytes[i] = ~bytes[i];
bmp.UnlockBits(data);
}
Note that this doesn't care about the colors and will invert those as well. If you wish to use a specific color, then the code will have to be modified a bit.
EDIT (thanks for Thomas notation)
public void ApplyInvert()
{
byte A, R, G, B;
Color pixelColor;
for (int y = 0; y < bitmapImage.Height; y++)
{
for (int x = 0; x < bitmapImage.Width; x++)
{
pixelColor = bitmapImage.GetPixel(x, y);
A = (byte)(255 - pixelColor.A);
R = pixelColor.R;
G = pixelColor.G;
B = pixelColor.B;
bitmapImage.SetPixel(x, y, Color.FromArgb((int)A, (int)R, (int)G, (int)B));
}
}
}
from here : Image Processing in C#: Inverting an image
For anyone who wants a fast method for inverting Bitmap colors without using unsafe:
public static void BitmapInvertColors(Bitmap bitmapImage)
{
var bitmapRead = bitmapImage.LockBits(new Rectangle(0, 0, bitmapImage.Width, bitmapImage.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppPArgb);
var bitmapLength = bitmapRead.Stride * bitmapRead.Height;
var bitmapBGRA = new byte[bitmapLength];
Marshal.Copy(bitmapRead.Scan0, bitmapBGRA, 0, bitmapLength);
bitmapImage.UnlockBits(bitmapRead);
for (int i = 0; i < bitmapLength; i += 4)
{
bitmapBGRA[i] = (byte)(255 - bitmapBGRA[i]);
bitmapBGRA[i + 1] = (byte)(255 - bitmapBGRA[i + 1]);
bitmapBGRA[i + 2] = (byte)(255 - bitmapBGRA[i + 2]);
// [i + 3] = ALPHA.
}
var bitmapWrite = bitmapImage.LockBits(new Rectangle(0, 0, bitmapImage.Width, bitmapImage.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppPArgb);
Marshal.Copy(bitmapBGRA, 0, bitmapWrite.Scan0, bitmapLength);
bitmapImage.UnlockBits(bitmapWrite);
}
Bitmap GetPixel and SetPixel are extremly slow, this method works by copying the Bitmap pixels into a byte array, which you can then loop through and change, before finally copying the pixels back.
When you say invert the transparent sections into color, are you storing the real colors in the PNG image just set to full transparency? A lot of programs will optimize a png by removing the color data from transparency so you can't reverse it.
Colors can be converted to transparency
But transparency (without underlying colors) cannot be converted to color.
If your lucky your PNG will be non optimized and still have the original color data intact, but if your doing this from user input then it won't work for a high percentage of cases.
In Photoshop you can select "Color" (the second from the bottom) to set the blending mode to the next lower layer:
If you have just a gradient on top of an image the result could look like this:
The description of the color blending mode I found somewhere is:
Color changes the hue and saturation of the lower layer to the hue and saturation of the upper layer but leaves luminosity alone.
My code so far is:
using(var g = Graphics.FromImage(canvas))
{
// draw the lower image
g.DrawImage(lowerImg, left, top);
// creating a gradient and draw on top
using (Brush brush = new LinearGradientBrush(new Rectangle(0, 0, canvasWidth, canvasHeight), Color.Violet, Color.Red, 20))
{
g.FillRectangle(brush, 0, 0, canvasWidth, canvasHeight);
}
}
But that is - of course - just painting over the lower image.
So the question is:
How can I draw an image on top of another image using the blending mode "color" as available in Photoshop?
EDIT:
To make it a bit more clear of what I want to achieve:
And if someone wants to use the images for testing:
Here is my solution. I've used Rich Newman's HSLColor class to convert between RGB and HSL values.
using (Bitmap lower = new Bitmap("lower.png"))
using (Bitmap upper = new Bitmap("upper.png"))
using (Bitmap output = new Bitmap(lower.Width, lower.Height))
{
int width = lower.Width;
int height = lower.Height;
var rect = new Rectangle(0, 0, width, height);
BitmapData lowerData = lower.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
BitmapData upperData = upper.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
BitmapData outputData = output.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);
unsafe
{
byte* lowerPointer = (byte*) lowerData.Scan0;
byte* upperPointer = (byte*) upperData.Scan0;
byte* outputPointer = (byte*) outputData.Scan0;
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
HSLColor lowerColor = new HSLColor(lowerPointer[2], lowerPointer[1], lowerPointer[0]);
HSLColor upperColor = new HSLColor(upperPointer[2], upperPointer[1], upperPointer[0]);
upperColor.Luminosity = lowerColor.Luminosity;
Color outputColor = (Color) upperColor;
outputPointer[0] = outputColor.B;
outputPointer[1] = outputColor.G;
outputPointer[2] = outputColor.R;
// Moving the pointers by 3 bytes per pixel
lowerPointer += 3;
upperPointer += 3;
outputPointer += 3;
}
// Moving the pointers to the next pixel row
lowerPointer += lowerData.Stride - (width * 3);
upperPointer += upperData.Stride - (width * 3);
outputPointer += outputData.Stride - (width * 3);
}
}
lower.UnlockBits(lowerData);
upper.UnlockBits(upperData);
output.UnlockBits(outputData);
// Drawing the output image
}
You will have to restructure your code so that you draw your gradient on a temporary bitmap, read each pixel from the temporary bitmap and canvas, and write a composed pixel to canvas. You should be able to find code converting between RGB and HSL colors, and once you can do that, setting the hue and saturation of pixels in canvas to the values from your temporary bitmap is trivial (though it's a bit harder if you want to use alpha values).
Here's a safe (and slower) version of the accepted answer for completeness.
using (var lower = new Bitmap(#"lower.png"))
using (var upper = new Bitmap(#"upper.png"))
using (var output = new Bitmap(lower.Width, lower.Height))
{
var width = lower.Width;
var height = lower.Height;
for (var i = 0; i < height; i++)
{
for (var j = 0; j < width; j++)
{
var upperPixel = upper.GetPixel(j, i);
var lowerPixel = lower.GetPixel(j, i);
var lowerColor = new HSLColor(lowerPixel.R, lowerPixel.G, lowerPixel.B);
var upperColor = new HSLColor(upperPixel.R, upperPixel.G, upperPixel.B) {Luminosity = lowerColor.Luminosity};
var outputColor = (Color)upperColor;
output.SetPixel(j, i, outputColor);
}
}
// Drawing the output image
}