I have a .png with transparency that I need to desaturate. I read I need to take the average R,G and B value of the bitmap then use:
G*.59
R*.3
B*.11
I calculate the average color in this way:
private Color Average_Color(Bitmap bitmap) {
Color c = new Color();
int pixel_number = 0;
int r = 0;
int g = 0;
int b = 0;
for (int i = 0; i < bitmap.Width; i++) {
for (int j = 0; j < bitmap.Height; j++) {
c = bitmap.GetPixel(i, j);
r += c.R;
g += c.G;
b += c.B;
pixel_number++;
}
}
c = Color.FromArgb(1, r / pixel_number, g / pixel_number, b / pixel_number);
return c;
}
Then, first to paint my texture, I set the Color in this way:
rgb = Average(bitmap);
GL.Color3(rgb.R * 0.59, rgb.G * 0.3, rgb.B * 0.11);
//here I draw my texture
I don't know why but it doesn't work (I get the texture with his original colors). I guess it's something wrong in Average_Color. Maybe because it's not a total opaque bitmap?
OpenGL expects its colors to be floats normalized from 0 to 1, but I suspect that your bitmap is reporting colors from 0-255.
Therefore I assume you're passing RGB values much greater than 1 to glColor, which get clamped to 1, so your texture looks the same.
Try
GL.Color3(rgb.R*0.59/255.f, rgb.G*0.3/255.f, rgb.B*0.11/255.f);
Try casting your variables to floats before the divide
Related
I am trying to get color from specific area in an Image.
Assume that , this is image , and I want to get color inside image.(the result should be red of the above image) This color may be different position in image. Because I don't know exact position of color where it starting, so I can't get exact result.
Until now, I cropped image giving manually position of x and y, and then cropped image and I got average color of cropped image. But I know , this is not exact color.
What I tried :
private RgbDto GetRGBvalueCroppedImage(Image croppedImage)
{
var avgRgb = new RgbDto();
var bm = new Bitmap(croppedImage);
BitmapData srcData = bm.LockBits(
new Rectangle(0, 0, bm.Width, bm.Height),
ImageLockMode.ReadOnly,
PixelFormat.Format32bppArgb);
int stride = srcData.Stride;
IntPtr Scan0 = srcData.Scan0;
long[] totals = new long[] { 0, 0, 0 };
int width = bm.Width;
int height = bm.Height;
unsafe
{
byte* p = (byte*)(void*)Scan0;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
for (int color = 0; color < 3; color++)
{
int idx = (y * stride) + x * 4 + color;
totals[color] += p[idx];
}
}
}
}
avgRgb.avgB = (int)totals[0] / (width * height);
avgRgb.avgG = (int)totals[1] / (width * height);
avgRgb.avgR = (int)totals[2] / (width * height);
return avgRgb;
}
How can I get exact position to crop? May be I can convert image to byte array, then I can find different color and take position of it and then crop. But I have no clue how do this.
You can use something this extension method to get dominant color in a region of an image in case they are not all the same
public static Color GetDominantColor(this Bitmap bitmap, int startX, int startY, int width, int height) {
var maxWidth = bitmap.Width;
var maxHeight = bitmap.Height;
//TODO: validate the region being requested
//Used for tally
int r = 0;
int g = 0;
int b = 0;
int totalPixels = 0;
for (int x = startX; x < (startX + width); x++) {
for (int y = startY; y < (startY + height); y++) {
Color c = bitmap.GetPixel(x, y);
r += Convert.ToInt32(c.R);
g += Convert.ToInt32(c.G);
b += Convert.ToInt32(c.B);
totalPixels++;
}
}
r /= totalPixels;
g /= totalPixels;
b /= totalPixels;
Color color = Color.FromArgb(255, (byte)r, (byte)g, (byte)b);
return color;
}
You can then use it like
Color pixelColor = myBitmap.GetDominantColor(xPixel, yPixel, 5, 5);
there is room for improvement, like using a Point and Size, or even a Rectangle
public static Color GetDominantColor(this Bitmap bitmap, Rectangle area) {
return bitmap.GetDominantColor(area.X, area.Y, area.Width, area.Height);
}
and following this link:
https://www.c-sharpcorner.com/UploadFile/0f68f2/color-detecting-in-an-image-in-C-Sharp/
If you want to get the image colors, you don't need to do any cropping at all. Just loop on image pixels and find the two different colors. (Assuming that you already know the image will have exactly 2 colors, as you said in comments). I've written a small function that will do that. However, I didn't test it in an IDE, so expect some small mistakes:
private static Color[] GetColors(Image image)
{
var bmp = new Bitmap(image);
var colors = new Color[2];
colors[0] = bmp.GetPixel(0, 0);
for (int i = 0; i < bmp.Width; i++)
{
for (int j = 0; j < bmp.Height; j++)
{
Color c = bmp.GetPixel(i, j);
if (c == colors[0]) continue;
colors[1] = c;
return colors;
}
}
return colors;
}
Converting a bitmap to grayscale is pretty easy with AForge:
public static Bitmap ConvertToGrayScale(this Bitmap me)
{
if (me == null)
return null;
// first convert to a grey scale image
var filterGreyScale = new Grayscale(0.2125, 0.7154, 0.0721);
me = filterGreyScale.Apply(me);
return me;
}
But I need something more tricky:
Imagine you want to convert everything to grayscale except for a circle in the middle of the bitmap. In other words: a circle in the middle of the given bitmap should keep its original colours.
Let's assume the radius of the circle is 20px, how should I approach this?
This can be accomplished using MaskedFilter with a mask that defines the circled area you describe. As the documentation states
Mask can be specified as .NET's managed Bitmap, as UnmanagedImage or
as byte array. In the case if mask is specified as image, it must be 8
bpp grayscale image. In all case mask size must be the same as size of
the image to process.
So the mask image has to be generated based on the source image's width and height.
I haven't compiled the following code but it should get you on your way. If the circle is always in the same spot, you could generate the image mask outside the method so that it doesn't have to be regenerated each time you apply the filter. Actually you could have the whole MaskedFilter generated outside the method that applies it if nothing changes but the source image.
public static Bitmap ConvertToGrayScale(this Bitmap me)
{
if (me == null)
return null;
var radius = 20, x = me.Width / 2, y = me.Height / 2;
using (Bitmap maskImage = new Bitmap(me.Width, me.Height, PixelFormat.Format8bppIndexed))
{
using (Graphics g = Graphics.FromImage(maskImage))
using (Brush b = new SolidBrush(ColorTranslator.FromHtml("#00000000")))
g.FillEllipse(b, x, y, radius, radius);
var maskedFilter = new MaskedFilter(new Grayscale(0.2125, 0.7154, 0.0721), maskImage);
return maskedFilter.Apply(me);
}
}
EDIT
The solution for this turned out to be a lot more trickier than I expected. The main problem was that the MaskedFilter doesn't allow the usage of filters that change the images format, which the Grayscale filter does (it changes the source to an 8bpp or 16 bpp image).
The following is the resulting code, which I have tested, with comments added to each part of the ConvertToGrayScale method explaining the logic behind it. The gray-scaled portion of the image has to be converted back to RGB since the Merge filter doesn't support merging two images with different formats.
static class MaskedImage
{
public static void DrawCircle(byte[,] img, int x, int y, int radius, byte val)
{
int west = Math.Max(0, x - radius),
east = Math.Min(x + radius, img.GetLength(1)),
north = Math.Max(0, y - radius),
south = Math.Min(y + radius, img.GetLength(0));
for (int i = north; i < south; i++)
for (int j = west; j < east; j++)
{
int dx = i - y;
int dy = j - x;
if (Math.Sqrt(dx * dx + dy * dy) < radius)
img[i, j] = val;
}
}
public static void Initialize(byte[,] arr, byte val)
{
for (int i = 0; i < arr.GetLength(0); i++)
for (int j = 0; j < arr.GetLength(1); j++)
arr[i, j] = val;
}
public static void Invert(byte[,] arr)
{
for (int i = 0; i < arr.GetLength(0); i++)
for (int j = 0; j < arr.GetLength(1); j++)
arr[i, j] = (byte)~arr[i, j];
}
public static Bitmap ConvertToGrayScale(this Bitmap me)
{
if (me == null)
return null;
int radius = 20, x = me.Width / 2, y = me.Height / 2;
// Generate a two-dimensional `byte` array that has the same size as the source image, which will be used as the mask.
byte[,] mask = new byte[me.Height, me.Width];
// Initialize all its elements to the value 0xFF (255 in decimal).
Initialize(mask, 0xFF);
// "Draw" a circle in the `byte` array setting the positions inside the circle with the value 0.
DrawCircle(mask, x, y, radius, 0);
var grayFilter = new Grayscale(0.2125, 0.7154, 0.0721);
var rgbFilter = new GrayscaleToRGB();
var maskFilter = new ApplyMask(mask);
// Apply the `Grayscale` filter to everything outside the circle, convert the resulting image back to RGB
Bitmap img = rgbFilter.Apply(grayFilter.Apply(maskFilter.Apply(me)));
// Invert the mask
Invert(mask);
// Get only the cirle in color from the original image
Bitmap circleImg = new ApplyMask(mask).Apply(me);
// Merge both the grayscaled part of the image and the circle in color in a single one.
return new Merge(img).Apply(circleImg);
}
}
I'm writing a program that removes an overlay from a png image using the mask (the overlay image)
having image 1 and 2 I want to achive image 3.
I have tried using lockbits and tried many things but I can't do the math right I think
rgbValues is the byte array of overlay and rgbValues2 is the byte array of given image.
for (int counter = 0; counter < rgbValues.Length; counter ++)
{
int x = (counter / 4) * 4;
if (rgbValues[x + 3] != 0)
{
if (rgbValues[x + 3] == rgbValues2[x + 3])
{
rgbValues2[counter] = 0;
}
else
{
float a1 = (float)rgbValues[counter];
float a2 = (float)rgbValues2[counter] ;
float b1 = (float)rgbValues[x + 3];
float b2 = (float)rgbValues2[x + 3];
rgbValues2[counter] = (byte)(2 * a2- a1);
}
}
}
I've tried this with your sample Images, although they are composed in the same big image and looks like it works. The following code doesn't use LockBits for simplicity, it just gives you the idea, that is how to calculate the base color (in the third image) from the blend color (in the second image) and the result color (in the first image):
public Image ExtractBaseImage(Bitmap resultImage, Bitmap blendImage) {
Bitmap bm = new Bitmap(resultImage.Width, resultImage.Height);
for (int i = 0; i < resultImage.Width; i++) {
for (int j = 0; j < resultImage.Height; j++) {
Color resultColor = resultImage.GetPixel(i, j);
Color blendColor = blendImage.GetPixel(i, j);
if (blendColor.A == 0) bm.SetPixel(i, j, resultColor);
else if(blendColor != resultColor){
float opacity = blendColor.A / 255f;
int r = Math.Max(0,Math.Min(255,(int) ((resultColor.R - (opacity) * blendColor.R) / (1-opacity))));
int g = Math.Max(0,Math.Min(255,(int)((resultColor.G - (opacity) * blendColor.G) / (1-opacity))));
int b = Math.Max(0,Math.Min(255,(int)((resultColor.B - (opacity) * blendColor.B) / (1-opacity))));
bm.SetPixel(i,j,Color.FromArgb(r,g,b));
}
}
}
return bm;
}
Usage: Suppose the images are numbered as you did to the images posted in your question, we have image1, image2, image3 variables:
image3 = ExtractBaseImage((Bitmap)image1, (Bitmap)image2);
I need to perform some mathematical operations in photographs, and for that I need the floating point grayscale version of an image (which might come from JPG, PNG or BMP files with various colordepths).
I used to do that in Python using PIL and scipy.ndimage, and it was very straightforward to convert to grayscale with PIL and then to an array of floating-point numbers with numpy, but now I need to do something similar in C#, and I'm confused how to do so.
I have read this very nice tutorial, that seems to be a recurring reference, but that only covers the "convert to grayscale" part, I am not sure how to get an array of doubles from a Bitmap, and then (at some moment) to convert it back to System.Drawing.Bitmap for viewing.
I'm sure there are loads of optimal ways to do this.
As #Groo points out perfectly in the comments section, one could use for instance the LockBits method to write and read pixel colors to and from a Bitmap instance.
Going even further, one could use the graphics card of the computer to do the actual computations.
Furthermore, the method Color ToGrayscaleColor(Color color) which turns a color into its
grayscale version is not optically correct. There is a set of ratios which actually need to be applied to the color component strengths. I just used 1, 1, 1 ratios. That's accceptable for me and probably horrible for an artist or a scientist.
In the comments section, #plinth was very nice to point out to this question you should look at, if you want to make an anatomically correct conversion: Converting RGB to grayscale/intensity
Just wanted to share this really easy to understand and implement solution:
First a little helper to turn a Color into it's grayscale version:
public static Color ToGrayscaleColor(Color color) {
var level = (byte)((color.R + color.G + color.B) / 3);
var result = Color.FromArgb(level, level, level);
return result;
}
Then for the color bitmap to grayscale bitmap conversion:
public static Bitmap ToGrayscale(Bitmap bitmap) {
var result = new Bitmap(bitmap.Width, bitmap.Height);
for (int x = 0; x < bitmap.Width; x++)
for (int y = 0; y < bitmap.Height; y++) {
var grayColor = ToGrayscaleColor(bitmap.GetPixel(x, y));
result.SetPixel(x, y, grayColor);
}
return result;
}
The doubles part is quite easy. The Bitmap object is a memory representation of the actual image which you can use in various operations. The colordepth and image format details are only the concern of loading and saving instances of Bitmap onto streams or files. We needn't care about those at this point:
public static double[,] FromGrayscaleToDoubles(Bitmap bitmap) {
var result = new double[bitmap.Width, bitmap.Height];
for (int x = 0; x < bitmap.Width; x++)
for (int y = 0; y < bitmap.Height; y++)
result[x, y] = (double)bitmap.GetPixel(x, y).R / 255;
return result;
}
And turning a double array back into a grayscale image:
public static Bitmap FromDoublesToGrayscal(double[,] doubles) {
var result = new Bitmap(doubles.GetLength(0), doubles.GetLength(1));
for (int x = 0; x < result.Width; x++)
for (int y = 0; y < result.Height; y++) {
int level = (int)Math.Round(doubles[x, y] * 255);
if (level > 255) level = 255; // just to be sure
if (level < 0) level = 0; // just to be sure
result.SetPixel(x, y, Color.FromArgb(level, level, level));
}
return result;
}
The following lines:
if (level > 255) level = 255; // just to be sure
level < 0) level = 0; // just to be sure
are really there in case you operate on the doubles and you want to allow room for little mistakes.
The final code, based mostly in tips taken from the comments, specifically the LockBits part (blog post here) and the perceptual balancing between R, G and B values (not paramount here, but something to know about):
private double[,] TransformaImagemEmArray(System.Drawing.Bitmap imagem) {
// Transforma a imagem de entrada em um array de doubles
// com os valores grayscale da imagem
BitmapData bitmap_data = imagem.LockBits(new System.Drawing.Rectangle(0,0,_foto_franjas_original.Width,_foto_franjas_original.Height),
ImageLockMode.ReadOnly, _foto_franjas_original.PixelFormat);
int pixelsize = System.Drawing.Image.GetPixelFormatSize(bitmap_data.PixelFormat)/8;
IntPtr pointer = bitmap_data.Scan0;
int nbytes = bitmap_data.Height * bitmap_data.Stride;
byte[] imagebytes = new byte[nbytes];
System.Runtime.InteropServices.Marshal.Copy(pointer, imagebytes, 0, nbytes);
double red;
double green;
double blue;
double gray;
var _grayscale_array = new Double[bitmap_data.Height, bitmap_data.Width];
if (pixelsize >= 3 ) {
for (int I = 0; I < bitmap_data.Height; I++) {
for (int J = 0; J < bitmap_data.Width; J++ ) {
int position = (I * bitmap_data.Stride) + (J * pixelsize);
blue = imagebytes[position];
green = imagebytes[position + 1];
red = imagebytes[position + 2];
gray = 0.299 * red + 0.587 * green + 0.114 * blue;
_grayscale_array[I,J] = gray;
}
}
}
_foto_franjas_original.UnlockBits(bitmap_data);
return _grayscale_array;
}
Is it possible to make the webcam of a pc acting as an ambient light sensor?
I am using .Net 4.5 framework in Windows 8 pro.
Philippe's answer to this question: How to calculate the average rgb color values of a bitmap has code that will calculate the average RGB values of a bitmat image (bm in his code):
BitmapData srcData = bm.LockBits(
new Rectangle(0, 0, bm.Width, bm.Height),
ImageLockMode.ReadOnly,
PixelFormat.Format32bppArgb);
int stride = srcData.Stride;
IntPtr Scan0 = dstData.Scan0;
long[] totals = new long[] {0,0,0};
int width = bm.Width;
int height = bm.Height;
unsafe
{
byte* p = (byte*) (void*) Scan0;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
for (int color = 0; color < 3; color++)
{
int idx = (y*stride) + x*4 + color;
totals[color] += p[idx];
}
}
}
}
int avgR = totals[0] / (width*height);
int avgG = totals[1] / (width*height);
int avgB = totals[2] / (width*height);
Once you have the average RGB values you can use Color.GetBrightness to determine how light or dark it is. GetBrightness will return a number between 0 and 1, 0 is black, 1 is white. You can use something like this:
Color imageColor = Color.FromARGB(avgR, avgG, avgB);
double brightness = imageColor.GetBrightness();
You could also convert the RGB values to HSL and look at the "L", that may be more accurate, I don't know.