get average color from bmp - c#

I am developing a taskbar for the 2nd screen(something like displayfusion).
However, I'm having difficulty at getting the right average color from the icon. For example Google Chrome/ When I hover it on the main taskbar it backgrounds turns yellow. With my code it turns orange/red.
This is what it looks now:
How can I get the right dominant/average color?
I use this code to calculate the average color:
public static Color getDominantColor(Bitmap bmp)
{
//Used for tally
int r = 0;
int g = 0;
int b = 0;
int total = 0;
for (int x = 0; x < bmp.Width; x++)
{
for (int y = 0; y < bmp.Height; y++)
{
Color clr = bmp.GetPixel(x, y);
r += clr.R;
g += clr.G;
b += clr.B;
total++;
}
}
//Calculate average
r /= total;
g /= total;
b /= total;
return Color.FromArgb(r, g, b);
}

The average color is not neccessarily the color most used. I recommend calculating the HUE of pixels which have saturation over a certain threshold, and use an array to create a histogram of the image. (How many times a certain hue value was used).
Then smooth the histogram (calculate local average values with both neighbours), then get the place where this smoothed histogram takes the maximal value.
You can get HSL values with:
Color.GetHue
Color.GetSaturation
Color.GetBrightness

Related

Approximate image from byte array

I wrote an application where some dots are floating around and if i assign a dot a Point, it will move on this position. Now i want to load an image, convert it to a monochrome image (Only pure black or white pixels - no shades of gray) and make each dot floating to a position where its representing a black pixel.
I've already done loading and converting a image that way and extraceted the pixels as a 1 dimensional byte[]. I've managed to iterate though this array with the following code:
int stride = width * 4;
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++)
{
int index = y * stride + 4 * x;
// array[index] <- Red
// array[index + 1] <- Green
// array[index + 2] <- Blue
// array[index + 3] <- Alpha
}
The byte array holds every pixel with 4 bytes (RGBA). So the array length is ImageHeight*ImageWidth*4 bytes. Either a pixel is Black (0, 0, 0, 255) or White (255, 255, 255, 255).
My problem now is that i'm not able to correctly approximate the black areas of the image with just n dots. In most cases I will have much less floating dots than there are black pixels in the array. So what i need is a method that gives me a Point[] that contains n Points that will represent only the black areas of the image as good as possible. Can someone help me out?
Loop though the array and find the points that their red, green and blue are 0
to get the Black dots:
List<Point> blackPoints = new List<Points>()
for(int i=0; i<array.Length; i+=4)
if(array[i] == 0 && array[i+1] == 0 && array[i+2] ==0) //alpha is not important
{
int tmp = i / 4;
blackPoints.Add(new Point(tmp%width, tmp/width));
}
Create Methods, to get the weight of a pixel based on its own and neighbors colors, and also a Method to find the weight of a block:
public static class Exts
{
public static int Weight(this points ps, int x, int y, int width, int height)
{
int weight = 0;
for(int i=Math.Max(x - 1, 0); i<Math.Min(width, x+1))
for(int j= Math.Max(y-1, 0), j<Math.Min(height, y+1))
if(ps.Any(a => a.X == i && a.Y == j)) weight++;
return weight;
}
public static int BlockWeight(this Point[] ps, int x, int y)
{
return ps.Count(a => a.X <= x+2 && a.Y<= y+2);
}
}
Now loop through the bitmap, with blocks of nine pixels (3x3) and if a blocks wieght is more than half (in this case more than or equal to 5), select a the point in this block that has heighest weight, to represent the black point:
List<Point> result = new List<Point>();
for(int i=0; i<width; i+=3)
for(int j=0; j< height; j+=3)
if(points.BlockWeight(i,j) >= 5)
result.Add(ps.Where(a => a.X <= x+2 && a.Y<= y+2).OrderByDescending(a => a.Weight(i, j, width, height)).First())

How to recognize "green" in a particular image?

Trying to find a solution for how to recognize if there is a "green" color on particular screenshot (image below).
The problem is that when using a plain RGB Bitmap, it doesn't work as I would like to because the image is taken from a screenshot of a webpage and it's kind of blurry, so many pixels doesn't look like "green".
I need somehow to understand how to define whether there is "green" color on a particular screenshot
I need somehow to know whether there is green color
Iterate all pixels and search for the desired color
Color c = Color.Green; //or the color you want to search
Bitmap bmp = new Bitmap(Image.FromFile(#"c:\file.bmp"));
bool containsgreen = false;
for (int w = 0; w < bmp.Width; w++)
for (int h = 0; h < bmp.Height; h++)
if (bmp.GetPixel(w, h) == c)
containsgreen = true;
If you're looking for a color range or similar colors, you could also calculate the color distance to add tolerance
public static double GetColourDistance(Color e1, Color e2)
{
return Math.Sqrt((e1.R - e2.R) * (e1.R - e2.R) + (e1.G - e2.G) * (e1.G - e2.G) + (e1.B - e2.B) * (e1.B - e2.B));
}
I am going to assume from your question, that when you say green, you don't mean that any pixel will have some positive value for the G component of the RGB color, but that you mean it looks visually green to a human.
If that is the case, I suggest a modification to #fubo's code that calculates "visually green". That would be when the G component is greater than the other components.
Now, this will return true for some sketchy greens, e.g. a green that is very, very dark or very, very light. If you want to filter those out, use a tolerance value of your choosing.
Here's the code:
bool HasGreen(int tolerance)
{
using (var bmp = new Bitmap(Image.FromFile(#"c:\file.bmp")))
{
for (int w = 0; w < bmp.Width; w++)
for (int h = 0; w < bmp.Height; h++)
if (IsGreenPixel(bmp.GetPixel(w, h), tolerance))
return true;
}
return false;
}
bool IsGreenPixel(Color color, int tolerance)
=> color.G > color.R + tolerance && color.G > color.B + tolerance;
If you're looking for "what is the main green color in the green colors", you could modify this algorithm further by doing counts of colors and dropping them into buckets (i.e. a histogram).

Drawing a matrix with a gradient of colors "Spectrogram"

After using STFT(Short-time Fourier transform) the output is a matrix that represents a 3d plot as though (A[X, Y] = M) A is the output matrix, X is the time , Y is the frequency, and the third dimension M is the amplitude illustrated by the intensity of the pixel color as in the following pictures:
Spectrogram 2
How do I draw the output matrix A with a gradient of colors like in the pictures in C#? Is there a library that contains a spectrogram control for C#?
Update:
After some modifications on the given algorithm I could draw the spectrogram, I didn't change the color palette except the first color changed to black but I don't know why it's very faded!
This one represents a sound saying
Bye Bye
Bye Bye Spectrogram
And this one of a pure sine wave so it's almost the same frequency all the time
Pure sine wave Spectrogram
The output is accepted it represents the frequencies of the input signal as expected, but i think there is a way to make the spectrogram as well illustrated as the ones in the examples, could you please take a look at my code and suggest modifications?
This is the event handler:
private void SpectrogramButton_Click(object sender, EventArgs e)
{
Complex[][] SpectrogramData = Fourier_Transform.STFT(/*signal:*/ samples, /*windowSize:*/ 512, /*hopSize:*/ 512);
SpectrogramBox.Image = Spectrogram.DrawSpectrogram(SpectrogramData, /*Interpolation Factor:*/ 1000, /*Height:*/ 256);
}
And this one is the drawing function after my modifications:
public static Bitmap DrawSpectrogram(Complex[][] Data, int InterpolationFactor, int Height)
{
// target size:
Size sz = new Size(Data.GetLength(0), Height);
Bitmap bmp = new Bitmap(sz.Width, sz.Height);
// the data array:
//double[,] data = new double[222, 222];
// step sizes:
float stepX = 1f * sz.Width / Data.GetLength(0);
float stepY = 1f * sz.Height / Data[0].GetLength(0);
// create a few stop colors:
List<Color> baseColors = new List<Color>(); // create a color list
baseColors.Add(Color.Black);
baseColors.Add(Color.LightSkyBlue);
baseColors.Add(Color.LightGreen);
baseColors.Add(Color.Yellow);
baseColors.Add(Color.Orange);
baseColors.Add(Color.Red);
// and the interpolate a larger number of grdient colors:
List<Color> colors = interpolateColors(baseColors, InterpolationFactor);
// a few boring test data
//Random rnd = new Random(1);
//for (int x = 0; x < data.GetLength(0); x++)
// for (int y = 0; y < data.GetLength(1); y++)
// {
// //data[x, y] = rnd.Next((int)(300 + Math.Sin(x * y / 999) * 200)) +
// // rnd.Next(x + y + 111);
// data[x, y] = 0;
// }
// now draw the data:
float Max = Complex.Max(Data);
using (Graphics G = Graphics.FromImage(bmp))
for (int x = 0; x < Data.GetLength(0); x++)
for (int y = 0; y < Data[0].GetLength(0); y++)
{
int Val = (int)Math.Ceiling((Data[x][y].Magnitude / Max) * (InterpolationFactor - 1));
using (SolidBrush brush = new SolidBrush(colors[(int)Val]))
G.FillRectangle(brush, x * stepX, (Data[0].GetLength(0) - y) * stepY, stepX, stepY);
}
// and display the result
return bmp;
}
I don't really understand the log thing that you are talking about in your answers, I'm sorry for my little knowledge.
Update:
This is the output after adding taking log10 to the magnitudes (negative values neglected):
This one of "Bye bye" from before:
A Shotgun Blast:
A Music Box:
I Think this output is acceptable, it is different from the examples I brought in the beginning but I think it's better.
No, there is no out of the box control I know of. There may well be outside libraries you can buy, of course, but shhh, you can't ask on SO about them..
In theory you could use, or I guess I should rather say abuse a Chart control for this. But since DataPoints are rather expensive objects, or at least more expensive than they look, this seems not advisable.
Instead you can simply draw the graph into a Bitmap yourself.
Step one is to decide on a gradient of colors. See the interpolateColors function here for an example of this!
Then you would simply do a double loop over the data using floats for the step and pixel sizes and do a Graphics.FillRectangle there.
Here is a simple example using GDI+ to create a Bitmap and a Winforms PictureBox for display. It doesn't add any axes to the graphic and fills it completely.
It first creates a few sample data and a gradient wih 1000 colors. Then it draws into a Bitmap and displays the result:
private void button6_Click(object sender, EventArgs e)
{
// target size:
Size sz = pictureBox1.ClientSize;
Bitmap bmp = new Bitmap(sz.Width, sz.Height);
// the data array:
double[,] data = new double[222, 222];
// step sizes:
float stepX = 1f * sz.Width / data.GetLength(0);
float stepY = 1f * sz.Height / data.GetLength(1);
// create a few stop colors:
List<Color> baseColors = new List<Color>(); // create a color list
baseColors.Add(Color.RoyalBlue);
baseColors.Add(Color.LightSkyBlue);
baseColors.Add(Color.LightGreen);
baseColors.Add(Color.Yellow);
baseColors.Add(Color.Orange);
baseColors.Add(Color.Red);
// and the interpolate a larger number of grdient colors:
List<Color> colors = interpolateColors(baseColors, 1000);
// a few boring test data
Random rnd = new Random(1);
for (int x = 0; x < data.GetLength(0); x++)
for (int y = 0; y < data.GetLength(1); y++)
{
data[x, y] = rnd.Next( (int) (300 + Math.Sin(x * y / 999) * 200 )) +
rnd.Next( x + y + 111);
}
// now draw the data:
using (Graphics G = Graphics.FromImage(bmp))
for (int x = 0; x < data.GetLength(0); x++)
for (int y = 0; y < data.GetLength(1); y++)
{
using (SolidBrush brush = new SolidBrush(colors[(int)data[x, y]]))
G.FillRectangle(brush, x * stepX, y * stepY, stepX, stepY);
}
// and display the result
pictureBox1.Image = bmp;
}
Here is the function from the link:
List<Color> interpolateColors(List<Color> stopColors, int count)
{
SortedDictionary<float, Color> gradient = new SortedDictionary<float, Color>();
for (int i = 0; i < stopColors.Count; i++)
gradient.Add(1f * i / (stopColors.Count - 1), stopColors[i]);
List<Color> ColorList = new List<Color>();
using (Bitmap bmp = new Bitmap(count, 1))
using (Graphics G = Graphics.FromImage(bmp))
{
Rectangle bmpCRect = new Rectangle(Point.Empty, bmp.Size);
LinearGradientBrush br = new LinearGradientBrush
(bmpCRect, Color.Empty, Color.Empty, 0, false);
ColorBlend cb = new ColorBlend();
cb.Positions = new float[gradient.Count];
for (int i = 0; i < gradient.Count; i++)
cb.Positions[i] = gradient.ElementAt(i).Key;
cb.Colors = gradient.Values.ToArray();
br.InterpolationColors = cb;
G.FillRectangle(br, bmpCRect);
for (int i = 0; i < count; i++) ColorList.Add(bmp.GetPixel(i, 0));
br.Dispose();
}
return ColorList;
}
You would probably want to draw axes with labels etc. You can use Graphics.DrawString or TextRenderer.DrawText to do so. Just leave enough space around the drawing area!
I used the data values cast to int as direct pointers into the color table.
Depending on your data you will need to scale them down or even use a log conversion. The first of your images show a logarithmic scale going from 100 to 20k, the second looks linear going from 0 to 100.
If you show us your data structure we can give you further hints how to adapt the code to use it..
You can create a bitmap as per the other answer. It's also common to use a color lookup table to convert FFT log magnitude to the color to use for each pixel or small rectangle.

C# Normalizing RGB and creating a new image

I am trying to create a program that accepts an image, recursively goes through each pixel, normalizes the pixel and re-creates a NEW image that looks the same as the original, but has normalized pixels instead.
public void parseJpeg(String jpegPath)
{
var normalizedRed = 0.0;
var normalizedGreen = 0.0;
var normalizedBlue = 0.0;
Bitmap normalizedImage = null;
var image = new Bitmap(jpegPath);
normalizedImage = new Bitmap(image.Width, image.Height);
for (int x = 0; x < image.Width; ++x)
{
for (int y = 0; y < image.Height; ++y)
{
Color color = image.GetPixel(x, y);
double exponent = 2;
double redDouble = Convert.ToDouble(color.R);
double blueDouble = Convert.ToDouble(color.B);
double greenDouble = Convert.ToDouble(color.G);
double redResult = Math.Pow(redDouble, exponent);
double blueResult = Math.Pow(blueDouble, exponent);
double greenResult = Math.Pow(greenDouble, exponent);
double totalResult = redResult + blueResult + greenResult;
normalizedRed = Convert.ToDouble(color.R) / Math.Sqrt(totalResult);
normalizedGreen = Convert.ToDouble(color.G) / Math.Sqrt(totalResult);
normalizedBlue = Convert.ToDouble(color.B) / Math.Sqrt(totalResult);
Color newCol = Color.FromArgb(Convert.ToInt32(normalizedRed), Convert.ToInt32(normalizedGreen), Convert.ToInt32(normalizedBlue));
normalizedImage.SetPixel(x, y, newCol);
}
}
normalizedImage.Save("C:\\Users\\username\\Desktop\\test1.jpeg");
resultsViewBox.AppendText("Process completed.\n");
}
Using the above code produces all black pixels and I do not understand why. When it normalizes it sets RGB = 1. After normalization, how do I set pixels with the NEW normalized value?
When I perform the below code, I get a black and blue image in my preview, but when I open the file it's blank. This is better than what I was getting before, which was ALL black pixels. This only works on one image though. So I am not sure how much of a step forward it is.
public void parseJpeg(String jpegPath)
{
Bitmap normalizedImage = null;
var image = new Bitmap(jpegPath);
normalizedImage = new Bitmap(image.Width, image.Height);
for (int x = 0; x < image.Width; ++x)
{
for (int y = 0; y < image.Height; ++y)
{
Color color = image.GetPixel(x, y);
float norm = (float)System.Math.Sqrt(color.R * color.R + color.B * color.B + color.G * color.G);
Color newCol = Color.FromArgb(Convert.ToInt32(norm));
normalizedImage.SetPixel(x, y, newCol);
}
}
normalizedImage.Save("C:\\Users\\username\\Desktop\\test1.jpeg");
resultsViewBox.AppendText("Process completed.\n");
}
I found the code for what I was trying to do:
http://www.lukehorvat.com/blog/normalizing-image-brightness-in-csharp/
public void parseJpeg(String jpegPath)
{
var image = new Bitmap(jpegPath);
normalizedImage = new Bitmap(image.Width, image.Height);
for (int x = 0; x < image.Width; ++x)
{
for (int y = 0; y < image.Height; ++y)
{
float pixelBrightness = image.GetPixel(x, y).GetBrightness();
minBrightness = Math.Min(minBrightness, pixelBrightness);
maxBrightness = Math.Max(maxBrightness, pixelBrightness);
}
}
for (int x = 0; x < image.Width; x++)
{
for (int y = 0; y < image.Height; y++)
{
Color pixelColor = image.GetPixel(x, y);
float normalizedPixelBrightness = (pixelColor.GetBrightness() - minBrightness) / (maxBrightness - minBrightness);
Color normalizedPixelColor = ColorConverter.ColorFromAhsb(pixelColor.A, pixelColor.GetHue(), pixelColor.GetSaturation(), normalizedPixelBrightness);
normalizedImage.SetPixel(x, y, normalizedPixelColor);
}
}
normalizedImage.Save("C:\\Users\\username\\Desktop\\test1.jpeg");
resultsViewBox.AppendText("Process completed.\n");
}
You are creating a new Bitmap and saving over the file for every pixel in your image. Move the
normalizedImage = new Bitmap(image.Width, image.Height);
line to before your loops, and the
normalizedImage.Save("C:\\Users\\username\\Desktop\\test1.jpeg");
line to after your loops.
Your normalization algorithm does not appear to be correct. Let's say your original color was red (255,0,0) Then your totalResult will be 65025, and your normalizedRed will be 255/sqrt(65025), which is 1, giving you a new normalized color of (1,0,0), which is essentially black.
Just as a note, your code will run a bit faster if you define all the doubles once outside the look and then assign them within the loop rather than defining and deleting each of the 8 doubles each iteration
Instead of messing with the colors you should use the brightness or luminosity factor to achieve normalization. Here is a link to the already answered question that can help you. you can convert each RGB pixel to HSL and minupulate L factor:
How do I normalize an image?
The code that you shared is actually a trim down version of HSL manipulation.

fixing color saturation using histogram normalization

I am developing a filter to work on images. After applying this filter to images, I am getting some RGB values which are more than 255 or less than 0. I can saturate this pixels to 255 and 0 respectively, but then the image is not good.
I want to find a way to normalize the pixel histograms so after normalization the values for RGB be between 0,255.
The code is as follow:
double max = outputPixel.Cast<double>().Max();
double targetMax = 300;
double min = outputPixel.Cast<double>().Min();
double targetMin = -50;
for (int c = 0; c < 3; c++)
{
for (int i = 0; i < outputPixel.GetLength(0); i++)
{
for (int j = 0; j < outputPixel.GetLength(1); j++)
{
outputPixel[i, j, c] = wb[c] * (((outputPixel[i, j, c] - min) * (targetMax-targetMin) / (max - min))+targetMin);
outputPixel[i, j, c] = Saturate(outputPixel[i, j, c]);
}
}
}
}
The outputPixel defined as
private static double[, ,] outputPixel;
and initialized as follow:
outputPixel = new double[inputImage.Width, inputImage.Height, 3];
The problem is:
If I don't do histogram normalization, then there is a lot of specke type noise on dark areas and most of the image is bleached out.
If I use histogram normalization, then I can see a red tint on white area (mainly cloud area).
One solution is to normalize histogram in HSB, but I can not use internal C# calculation for HSB as values for RGB are bigger (or smalle) than 255 (0).
What Can I do in C#?

Categories

Resources