contrast with color matrix - c#

Hi I want to implement contrast filter in my application like this link or this contrast but with color matrix and track bar for value
I already found color matrix for it
float c = mytrackbar.value * 0.01f //maxTrackbarValue = 100, minTrackbarValue = -100
float t = 0.01f;
cmPicture = new ColorMatrix(new float[][] {
new float[] {c,0,0,0,0},
new float[] {0,c,0,0,0},
new float[] {0,0,c,0,0},
new float[] {0,0,0,1,0},
new float[] {t,t,t,0,1}
});
but the result is very different. I try to change 0.01f in ~c~ and 0.01f in ~t~ value but it only gives result like brightness
(ex : c = mytrackbar.value * 0.04f )
I wonder what ~c~ & ~t~ value and how many max and min range i should used for created contrast
Update #Nico
private void myTrackBar_ValueChanged(object sender, EventArgs e) {
imageHandle = imageHandleTemp.Bitmap;
myNumericUpDown.Text = myTrackBar.Value.ToString();
float value = myTrackBar.Value * 0.01f;
Bitmap bmpInverted = new Bitmap(imageHandle.Width, imageHandle.Height);
ImageAttributes ia = new ImageAttributes();
ColorMatrix cmPicture = new ColorMatrix();
float c = value;
float t = 0.01f;
cmPicture = new ColorMatrix(new float[][] {
new float[] {c,0,0,0,0},
new float[] {0,c,0,0,0},
new float[] {0,0,c,0,0},
new float[] {0,0,0,1,0},
new float[] {t,t,t,0,1}
});
ia.SetColorMatrix(cmPicture);
Graphics g = Graphics.FromImage(bmpInverted);
g.DrawImage(imageHandle, new Rectangle(0, 0, imageHandle.Width, imageHandle.Height), 0, 0, imageHandle.Width, imageHandle.Height, GraphicsUnit.Pixel, ia);
g.Dispose();
Image<Bgr, Byte> myImage = new Image<Bgr, byte>(bmpInverted);
imageBoxCamera.Image = myImage;
}

The t value must be derived from the new contrast value c. so change its assignment like this:
float t = (1.0f - c) / 2.0f;
According to this nice link to the ColorMatrix Guide the rest of the matrix code seems to be OK.
Note: I was wrong about the range of c!! The value of c is not an absolute value but it is the factor that should be applied! So to double the contrast it should be 2f.
Note 2: Your code is not quite clear about source and target; and since you are changing contrast on the fly with the trackbar it should be clear that until the change is applied,
The intermediate results must always be calculated from the same original bitmap.
Also, that the trackbar should be so intialized that it starts with a value of 1.
And finally, in order to reduce contrast values should translate to 0 < c < 1.
Nico's suggestion c = 1+ value; would work nicely with your original range -100 to +100 with an intial value of 0 and your factor 0.01f.

Here are some further explanations about the maths behind the process. If you are going to accept an answer, accept TaW's answer, he was quicker than me.
Adjusting the contrast basically does the following:
It shifts all pixel values by -0.5 (so that a medium-gray becomes 0)
It applies a factor to all pixel values
It reverts the shift
So in a formula:
newValue = factor * (oldValue - 0.5) + 0.5
= factor * oldValue - factor * 0.5 + 0.5
= factor * oldValue + 0.5 * (1 - factor)
For a single-channel image we can transform this into a matrix multiplication:
(newValue, 1) = (oldValue, 1) * / factor , 0\ <-- this is c, 0
\ 0.5 * (1 - factor) , 1/ <-- this is t, 1
For multi-channel images, the 2x2 matrix becomes a 5x5 (four channels + w-channel for translations) matrix with c and t at the positions you already specified.
Keep in mind that a factor of 1 does not change anything. A factor of 0 makes the whole image medium-gray. So you may want to adjust your calculation of c:
c = 1 + value;
t = 0.5f * (1.0f - c);

Related

Leaflet Maps with Custom Tiles Zoom offset x y

I'm trying to use leaflet to render large images using x,y coordinates like so:
var map = L.map('map', {
crs: L.CRS.Simple,
attributionControl: false,
reuseTiles:true,
}).setView([0, 0], 1);
The problem is that when I zoom I seem to get an offset. So as I continually zoom in the map appears to shift.
I am drawing the image on the backend using C# and GDI+ so it's quite possible that I am getting code this wrong:
private void DrawLine(int x, int y, int z, int squareSize, Graphics g, Shape shape, Pen drawPen)
{
Line line = (Line)shape;
var scalingFactor = 0.1;
var zoom = (z * (scalingFactor));
double startScaledX = (line.StartPoint.X * zoom) + ((squareSize * -1) * x);
double startScaledY = (line.StartPoint.Y * -1 * zoom) + ((squareSize * -1) * y);
double endScaledX = (line.EndPoint.X * zoom) + ((squareSize * -1) * x);
double endScaledY = (line.EndPoint.Y * -1 * zoom) + ((squareSize * -1) * y);
var width = Math.Abs(endScaledX - startScaledX);
var height = Math.Abs(endScaledY - startScaledY);
var startPoint = new System.Drawing.PointF((float)startScaledX, (float)startScaledY);
var endPoint = new System.Drawing.PointF((float)endScaledX, (float)endScaledY);
var rectDrawBounds = (new RectangleF((float)startScaledX, (float)startScaledY, (float)width, (float)height));
var rectTileBounds = new RectangleF(0, 0, 256, 256);
g.DrawLine(drawPen, startPoint, endPoint);
}
I have noticed that if I zoom in and out at [0,0] then the zoom works perfectly. Everything else seems to shift the map.
I would appreciate any help that you can offer.
In Leaflet's L.CRS.Simple, the map scale grows by a factor of 2 every zoom level. In other words:
scale = 2**z;
or
scale = Math.pow(2,z);
or
scale = 1<<z;
or
At zoom level 0, a 256-pixel tile covers 256 map units. One map unit spans over 1 pixel.
At zoom level 1, a 256-pixel tile covers 128 map units. One map unit spans over 2 pixels.
At zoom level 2, a 256-pixel tile covers 64 map units. One map unit spans over 4 pixels.
At zoom level n, a 256-pixel tile covers 256/2n map units. One map unit spans over 2n pixels.
For reference, see https://github.com/Leaflet/Leaflet/blob/master/src/geo/crs/CRS.Simple.js
Fix your z, scalingFactor and zoom calculations and relationships accordingly.

How to draw an audio waveform to a bitmap

I am attempting to extract the audio content of a wav file and export the resultant waveform as an image (bmp/jpg/png).
So I have found the following code which draws a sine wave and works as expected:
string filename = #"C:\0\test.bmp";
int width = 640;
int height = 480;
Bitmap b = new Bitmap(width, height);
for (int i = 0; i < width; i++)
{
int y = (int)((Math.Sin((double)i * 2.0 * Math.PI / width) + 1.0) * (height - 1) / 2.0);
b.SetPixel(i, y, Color.Black);
}
b.Save(filename);
This works completely as expected, what I would like to do is replace
int y = (int)((Math.Sin((double)i * 2.0 * Math.PI / width) + 1.0) * (height - 1) / 2.0);
with something like
int y = converted and scaled float from monoWaveFileFloatValues
So how would I best go about doing this in the simplest manner possible?
I have 2 basic issues I need to deal with (i think)
convert float to int in a way which does not loose information, this is due to SetPixel(i, y, Color.Black); where x & y are both int
sample skipping on the x axis so the waveform fits into the defined space audio length / image width give the number of samples to average out intensity over which would be represented by a single pixel
The other options is find another method of plotting the waveform which does not rely on the method noted above. Using a chart might be a good method, but I would like to be able to render the image directly if possible
This is all to be run from a console application and I have the audio data (minus the header) already in a float array.
UPDATE 1
The following code enabled me to draw the required output using System.Windows.Forms.DataVisualization.Charting but it took about 30 seconds to process 27776 samples and whilst it does do what I need, it is far too slow to be useful. So I am still looking towards a solution which will draw the bitmap directly.
System.Windows.Forms.DataVisualization.Charting.Chart chart = new System.Windows.Forms.DataVisualization.Charting.Chart();
chart.Size = new System.Drawing.Size(640, 320);
chart.ChartAreas.Add("ChartArea1");
chart.Legends.Add("legend1");
// Plot {sin(x), 0, 2pi}
chart.Series.Add("sin");
chart.Series["sin"].LegendText = args[0];
chart.Series["sin"].ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.Spline;
//for (double x = 0; x < 2 * Math.PI; x += 0.01)
for (int x = 0; x < audioDataLength; x ++)
{
//chart.Series["sin"].Points.AddXY(x, Math.Sin(x));
chart.Series["sin"].Points.AddXY(x, leftChannel[x]);
}
// Save sin_0_2pi.png image file
chart.SaveImage(#"c:\tmp\example.png", System.Drawing.Imaging.ImageFormat.Png);
Output shown below:
So I managed to figure it out using a code sample found here, though I made some minor changes to the way I interact with it.
public static Bitmap DrawNormalizedAudio(List<float> data, Color foreColor, Color backColor, Size imageSize, string imageFilename)
{
Bitmap bmp = new Bitmap(imageSize.Width, imageSize.Height);
int BORDER_WIDTH = 0;
float width = bmp.Width - (2 * BORDER_WIDTH);
float height = bmp.Height - (2 * BORDER_WIDTH);
using (Graphics g = Graphics.FromImage(bmp))
{
g.Clear(backColor);
Pen pen = new Pen(foreColor);
float size = data.Count;
for (float iPixel = 0; iPixel < width; iPixel += 1)
{
// determine start and end points within WAV
int start = (int)(iPixel * (size / width));
int end = (int)((iPixel + 1) * (size / width));
if (end > data.Count)
end = data.Count;
float posAvg, negAvg;
averages(data, start, end, out posAvg, out negAvg);
float yMax = BORDER_WIDTH + height - ((posAvg + 1) * .5f * height);
float yMin = BORDER_WIDTH + height - ((negAvg + 1) * .5f * height);
g.DrawLine(pen, iPixel + BORDER_WIDTH, yMax, iPixel + BORDER_WIDTH, yMin);
}
}
bmp.Save(imageFilename);
bmp.Dispose();
return null;
}
private static void averages(List<float> data, int startIndex, int endIndex, out float posAvg, out float negAvg)
{
posAvg = 0.0f;
negAvg = 0.0f;
int posCount = 0, negCount = 0;
for (int i = startIndex; i < endIndex; i++)
{
if (data[i] > 0)
{
posCount++;
posAvg += data[i];
}
else
{
negCount++;
negAvg += data[i];
}
}
if (posCount > 0)
posAvg /= posCount;
if (negCount > 0)
negAvg /= negCount;
}
In order to get it working I had to do a couple of things prior to calling the method DrawNormalizedAudio you can see below what I needed to do:
Size imageSize = new Size();
imageSize.Width = 1000;
imageSize.Height = 500;
List<float> lst = leftChannel.OfType<float>().ToList(); //change float array to float list - see link below
DrawNormalizedAudio(lst, Color.Red, Color.White, imageSize, #"c:\tmp\example2.png");
* change float array to float list
The result of this is as follows, a waveform representation of a hand clap wav sample:
I am quite sure there needs to be some updates/revisions to the code, but it's a start and hopefully this will assist someone else who is trying to do the same thing I was.
If you can see any improvements that can be made, let me know.
UPDATES
NaN issue mentioned in the comments now resolved and code above updated.
Waveform Image updated to represent output fixed by removal of NaN values as noted in point 1.
UPDATE 1
Average level (not RMS) was determined by summing the max level for each sample point and dividing by the total number of samples. Examples of this can be seen below:
Silent Wav File:
Hand Clap Wav File:
Brownian, Pink & White Noise Wav File:
Here is a variation you may want to study. It scales the Graphics object so it can use the float data directly.
Note how I translate (i.e. move) the drawing area twice so I can do the drawing more conveniently!
It also uses the DrawLines method for drawing. The benefit in addition to speed is that the lines may be semi-transparent or thicker than one pixel without getting artifacts at the joints. You can see the center line shine through.
To do this I convert the float data to a List<PointF> using a little Linq magick.
I also make sure to put all GDI+ objects I create in using clause so they will get disposed of properly.
...
using System.Windows.Forms;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
..
..
class Program
{
static void Main(string[] args)
{
float[] data = initData(10000);
Size imgSize = new Size(1000, 400);
Bitmap bmp = drawGraph(data, imgSize , Color.Green, Color.Black);
bmp.Save("D:\\wave.png", ImageFormat.Png);
}
static float[] initData(int count)
{
float[] data = new float[count];
for (int i = 0; i < count; i++ )
{
data[i] = (float) ((Math.Sin(i / 12f) * 880 + Math.Sin(i / 15f) * 440
+ Math.Sin(i / 66) * 110) / Math.Pow( (i+1), 0.33f));
}
return data;
}
static Bitmap drawGraph(float[] data, Size size, Color ForeColor, Color BackColor)
{
Bitmap bmp = new System.Drawing.Bitmap(size.Width, size.Height,
PixelFormat.Format32bppArgb);
Padding borders = new Padding(20, 20, 10, 50);
Rectangle plotArea = new Rectangle(borders.Left, borders.Top,
size.Width - borders.Left - borders.Right,
size.Height - borders.Top - borders.Bottom);
using (Graphics g = Graphics.FromImage(bmp))
using (Pen pen = new Pen(Color.FromArgb(224, ForeColor),1.75f))
{
g.SmoothingMode = SmoothingMode.AntiAlias;
g.Clear(Color.Silver);
using (SolidBrush brush = new SolidBrush(BackColor))
g.FillRectangle(brush, plotArea);
g.DrawRectangle(Pens.LightGoldenrodYellow, plotArea);
g.TranslateTransform(plotArea.Left, plotArea.Top);
g.DrawLine(Pens.White, 0, plotArea.Height / 2,
plotArea.Width, plotArea.Height / 2);
float dataHeight = Math.Max( data.Max(), - data.Min()) * 2;
float yScale = 1f * plotArea.Height / dataHeight;
float xScale = 1f * plotArea.Width / data.Length;
g.ScaleTransform(xScale, yScale);
g.TranslateTransform(0, dataHeight / 2);
var points = data.ToList().Select((y, x) => new { x, y })
.Select(p => new PointF(p.x, p.y)).ToList();
g.DrawLines(pen, points.ToArray());
g.ResetTransform();
g.DrawString(data.Length.ToString("###,###,###,##0") + " points plotted.",
new Font("Consolas", 14f), Brushes.Black,
plotArea.Left, plotArea.Bottom + 2f);
}
return bmp;
}
}

What's the correct math to fade a color?

I'm trying to fade a color, let's say Yellow to White over a period of time. I've got the timer worked out, and I'm applying the new color just fine as well, but the fade isn't smooth (e.g. it fades into some weird colors before it gets to White, some of which are darker than their predecessor on the "fade chain" let's call it). I'm confident that's because the math is wrong, but I just can't seem to find a good example of the math for me to modify what I'm doing.
I even pulled the basics of the ColorCeiling code from this question: Fade a color to white (increasing brightness)
Right now I take a color, and call an extension method Increase:
dataGridViewResults.Rows[0].DefaultCellStyle.BackColor.Increase(50);
public static Color Increase(this Color color, byte offset)
{
return Color.FromArgb(
color.A.ColorCeiling(offset),
color.R.ColorCeiling(offset),
color.G.ColorCeiling(offset),
color.B.ColorCeiling(offset));
}
and as you can see, each color is then modified by an offset with a ceiling in mind to keep exceptions from being thrown. That extension method, ColorCeiling looks like this:
public static int ColorCeiling(this byte val, byte modifier, byte ceiling = 255)
{
return (val + modifier > ceiling) ? ceiling : val + modifier;
}
Now, I'm confident that ColorCeiling is the problem, but I honestly just can't find the math. I honestly feel like just incrementing the ARGB is almost certainly the wrong approach, it seems like you'd say I want you to be 50% lighter, but I just don't know what that means as far as code.
Here's an idea that might work.
Don't do the math in Red-Green-Blue space. Instead treat your colour as having three components:
Hue (is it red, orange, yellow, green, blue, violet, etc),
Saturation (how intense is the color?)
Value (how much blackness is in the color?)
There are algorithms for converting from RGB to HSV; look them up. This is a good place to start:
http://en.wikipedia.org/wiki/HSL_and_HSV
Now when you are fading from one color to the other, take steps along the HSV axes, not along the RGB axes.
A gradient is what you're looking for. YOu'll have to seperate them and recombine them like so..
int rMax = Color.Yellow.R;
int rMin = Color.White.R;
int bMax = Color.Yellow.B;
int bMin = Color.White.B;
int gMax = Color.Yellow.G;
int gMin = Color.White.G;
var colorList = new List<Color>();
for(int i=0; i<size; i++)
{
var rAverage = rMin + (int)((rMax - rMin) * i / size);
var bAverage = bMin + (int)((bMax - bMin) * i / size);
var gAverage = gMin + (int)((gMax - gMin) * i / size);
colorList.Add(Color.FromArgb(rAverage, gAverage, bAverage));
}
Percentage implies multiplication, not addition. In my really simple JavaScript Color class, I do this sort of thing by defining two colors and blending the RGB dimensions.
From my Color class:
this.getBlendedColor = function(color, percent) {
// limit percent between 0 and 1.
// this percent is the amount of 'color' rgb components to use
var p = percent > 0 ? percent : 0;
p = p < 1 ? p : 1;
// amount of 'this' rgb components to use
var tp = 1 - p;
// blend the colors
var r = Math.round(tp * this.r + p * color.r);
var g = Math.round(tp * this.g + p * color.g);
var b = Math.round(tp * this.b + p * color.b);
// return new color object
return new Color(r, g, b);
} // getBlendedColor ()
So, if you had some Color c, and you wanted to get it 50% whiter, you'd do this:
var newColor = c.getBlendedColor(new Color('#ffffff'), 0.50);
If you're using the alpha channel, it can be explicitly included without causing any trouble -- both colors in you example are probably at 100% opacity.
An example gradient using simple RGB Color blending math: http://jsfiddle.net/2MymY/1/
Here is some code I use to fade from one color to another in the RGB color space. It works quite well for my purposes. You might have to adapt it to return the colors in steps. Currently for each i you will have a curClr that is i/discreteUnits percent between baseClr and fadeTo.
int discreteUnits = 10;
Color fadeTo = Color.Green;
Color baseClr = Color.Red;
float correctionFactor = 0.0f;
float corFactorStep = 1.0f / discreteUnits;
for(int i = 0; i < discreteUnits; i++)
{
correctionFactor += corFactorStep;
float red = (fadeTo.R - baseClr.R) * correctionFactor + baseClr.R;
float green = (fadeTo.G - baseClr.G) * correctionFactor + baseClr.G;
float blue = (fadeTo.B - baseClr.B) * correctionFactor + baseClr.B;
Color curClr = Color.FromArgb(baseClr.A, (int)red, (int)green, (int)blue);
}
Credit to this site for giving me a starting place: http://www.pvladov.com/2012/09/make-color-lighter-or-darker.html

How to rotate image x degrees in c#?

I have done some searching and i can not find any function thats doing what i whant it todo.
I have a image file of a scanned document with text, but the document is some degrees rotated so i whant to rotate it so the text being inline with each other.
In a perfect world its should be a function doing this automaticly but i can not find anything and what i understand to get it to work automaticly its needed to be some analyze of the image and i think its to big thing todo.
But then i have done a tool to rotate the image on a website manually, but now i need a function to save the rotation to the image file.
This seems to be some differents methods for but no one i tested doing what i whant.
The function i have finded that works almost like i whant is:
public static Bitmap RotateImg(Bitmap bmp, float angle, Color bkColor) {
int w = bmp.Width;
int h = bmp.Height;
PixelFormat pf = default(PixelFormat);
if (bkColor == Color.Transparent)
{
pf = PixelFormat.Format32bppArgb;
}
else
{
pf = bmp.PixelFormat;
}
Bitmap tempImg = new Bitmap(w, h, pf);
Graphics g = Graphics.FromImage(tempImg);
g.Clear(bkColor);
g.DrawImageUnscaled(bmp, 1, 1);
g.Dispose();
GraphicsPath path = new GraphicsPath();
path.AddRectangle(new RectangleF(0f, 0f, w, h));
Matrix mtrx = new Matrix();
//Using System.Drawing.Drawing2D.Matrix class
mtrx.Rotate(angle);
RectangleF rct = path.GetBounds(mtrx);
Bitmap newImg = new Bitmap(Convert.ToInt32(rct.Width), Convert.ToInt32(rct.Height), pf);
g = Graphics.FromImage(newImg);
g.Clear(bkColor);
g.TranslateTransform(-rct.X, -rct.Y);
g.RotateTransform(angle);
g.InterpolationMode = InterpolationMode.HighQualityBilinear;
g.DrawImageUnscaled(tempImg, 0, 0);
g.Dispose();
tempImg.Dispose();
return newImg; }
But this do not change the height and width of the image file so the image file is in the same size but the image "object" has been scaled and rotated.
Any idea how i can do this good?
Answer
I find the solution that worked with my image that has a resolution on 300 at a old answer here.
If I've understood your question correctly, you essentially want to work out the new size of an image once rotated, and how to position the rotated image in it's new bitmap.
The diagram hopefully helps make clear the solution. Here is a bit of pseudo code:
sinVal = abs(sin(angle))
cosVal = abs(cos(angle))
newImgWidth = sinVal * oldImgHeight + cosVal * oldImgWidth
newImgHeight = sinVal * oldImgWidth + cosVal * oldImgHeight
originX = 0
originY = sinVal * oldImgWidth
You want to make the new image from the newImgWidth and newImgHeight, and then perform a rotation around the origin (originX, originY) and then render the image to this point. This will fall over if the angle (in degrees) isn't between -90 and 0 degrees (depicted). If it is between 0 and 90 degrees, then you just change the origin:
originX = sinVal * oldImgHeight
originY = 0
If it is in the range 90 degrees to 270 degrees (-90 degrees) then it is a little tricker (see example code below).
Your code re-written (briefly tested) - it is slightly dodgy but seems to work:
public static Bitmap RotateImg(Bitmap bmp, float angle, Color bkColor)
{
angle = angle % 360;
if (angle > 180)
angle -= 360;
System.Drawing.Imaging.PixelFormat pf = default(System.Drawing.Imaging.PixelFormat);
if (bkColor == Color.Transparent)
{
pf = System.Drawing.Imaging.PixelFormat.Format32bppArgb;
}
else
{
pf = bmp.PixelFormat;
}
float sin = (float)Math.Abs(Math.Sin(angle * Math.PI / 180.0)); // this function takes radians
float cos = (float)Math.Abs(Math.Cos(angle * Math.PI / 180.0)); // this one too
float newImgWidth = sin * bmp.Height + cos * bmp.Width;
float newImgHeight = sin * bmp.Width + cos * bmp.Height;
float originX = 0f;
float originY = 0f;
if (angle > 0)
{
if (angle <= 90)
originX = sin * bmp.Height;
else
{
originX = newImgWidth;
originY = newImgHeight - sin * bmp.Width;
}
}
else
{
if (angle >= -90)
originY = sin * bmp.Width;
else
{
originX = newImgWidth - sin * bmp.Height;
originY = newImgHeight;
}
}
Bitmap newImg = new Bitmap((int)newImgWidth, (int)newImgHeight, pf);
Graphics g = Graphics.FromImage(newImg);
g.Clear(bkColor);
g.TranslateTransform(originX, originY); // offset the origin to our calculated values
g.RotateTransform(angle); // set up rotate
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBilinear;
g.DrawImageUnscaled(bmp, 0, 0); // draw the image at 0, 0
g.Dispose();
return newImg;
}
Note the Degrees to Radians Conversion (180 Degrees == Pi Radians) for the trig functions
Edit: big issue was negative sin values, and me getting width/height confused when calculating origin x/y - this should work fine now (tested)
Edit: modified code to handle any angle
This is strictly a comment to the nice answer by VisualMelon above, But I'm not allowed to add comments...
There are two tiny bugs in the code
a) The first check after the modulus should either be split into two, or changed to e.g.
if (180<Math.Abs(angle)) angle -= 360*Math.Sign(angle);
Otherwise angles between -360 and -180 will fail, as only +180 to +360 were handled
b) Just after the newImg assignment, a resolution assignment is missing, e.g.
newImg.SetResolution(bmp.HorizontalResolution, bmp.VerticalResolution);
If omitted the image will be scaled if the source is not 96 dpi.
....And splitting sticks, the intermediate calculations of dimensions and offsets ought to be kept in double, and only reduced to float last

How to calculate color based on a range of values in C#?

var colors = new Color[] {
Color.Blue,
Color.Green,
Color.Yellow,
Color.Orange,
Color.Red
};
var min = 0;
var max = 400;
I'm trying to get the color in between these values based on another number. So for example if I wanted to the color for the value 350, it would be 50% orange and 50% red.
EDIT - Reworded for clarity
The only way I can think of doing it is creating a gradient image in photoshop, then calculating the offset and grabbing the pixel RGB value. However this seems extremely hacky and I would like to do it by some kind of calculation.
Any ideas?
You could use linear interpolation to mix the R, G and B values (and A if you want). Here's some example code for a Windows Form project. Just add a trackbar with range 0 to 400 and wire up the trackbar's scroll event to the handler below:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
byte interpolate(byte a, byte b, double p)
{
return (byte)(a * (1 - p) + b * p);
}
private void trackBar1_Scroll(object sender, EventArgs e)
{
int v = trackBar1.Value;
BackColor = getColor(v);
}
private Color getColor(int v)
{
SortedDictionary<int, Color> d = new SortedDictionary<int, Color>();
d.Add(0, Color.Blue);
d.Add(100, Color.Green);
d.Add(200, Color.Yellow);
d.Add(300, Color.Orange);
d.Add(400, Color.Red);
KeyValuePair<int, Color> kvp_previous = new KeyValuePair<int,Color>(-1, Color.Black);
foreach (KeyValuePair<int, Color> kvp in d)
{
if (kvp.Key > v)
{
double p = (v - kvp_previous.Key) / (double)(kvp.Key - kvp_previous.Key);
Color a = kvp_previous.Value;
Color b = kvp.Value;
Color c = Color.FromArgb(
interpolate(a.R, b.R, p),
interpolate(a.G, b.G, p),
interpolate(a.B, b.B, p));
return c;
}
kvp_previous = kvp;
}
return Color.Black;
}
}
You could also use this idea with HSL colors as suggested by nobugz.
Note: This code is just proof-of-concept. In a real application you would want to create a class to encapsulate the color choosing logic, make it more customizable, and error handling, etc. It's also not optimized for speed. If speed is an important consideration then you should probably use a look-up table instead.
There is one small bug in Mark Byers answer, it needs to be modified to:
if (kvp.Key > v)
{
double p = (v - kvp_previous.Key) / (double)(kvp.Key - kvp_previous.Key);
Color a = kvp_previous.Value;
Color b = kvp.Value;
Color c = Color.FromArgb(
interpolate(a.R, b.R, p),
interpolate(a.G, b.G, p),
interpolate(a.B, b.B, p));
return c;
}
else if (kvp.Key == v)
{
return kvp.Value;
}
Otherwise anything that is equal to one of the input points is returned as Black.
(I don't have enough reputation to comment on his answer so as far as I know this is my only recourse)
Another way to generalize solution
/// <summary>
/// Interpolate colors 0.0 - 1.0
/// </summary>
public static Color Interpolate(double percent, params Color[] colors)
{
int left = (int)Math.Floor(percent * (colors.Length - 1));
int right = (int)Math.Ceiling(percent * (colors.Length - 1));
Color colorLeft = colors[left];
Color colorRight = colors[right];
double step = 1.0 / (colors.Length - 1);
double percentRight = (percent - (left * step)) / step;
double percentLeft = 1.0 - percentRight;
Color outputColor = new Color();
outputColor.R = (byte)(colorLeft.R * percentLeft + colorRight.R * percentRight);
outputColor.G = (byte)(colorLeft.G * percentLeft + colorRight.G * percentRight);
outputColor.B = (byte)(colorLeft.B * percentLeft + colorRight.B * percentRight);
outputColor.A = (byte)(colorLeft.A * percentLeft + colorRight.A * percentRight);
return outputColor;
}
Something like this might work for you...though, this is completely untested. The idea is that you calculate what two colors you need, and after that you mix these two based on the percentage value which you can calculate. As said, completely untested.
using System.Convert;
public static Color oddColorFunction(int value)
{
Color colors = new Color[] { Color.Blue, Color.Green, Color.Yellow, Color.Orange, Color.Red };
int min = 0;
int max = 400;
decimal range = max / colors.Length;
Color leftColor = ToInt32(Decimal.Floor(value / range));
Color rightColor = ToInt32(Decimal.Ceiling(value / range));
return mixColors(colors[leftColor], colors[rightColor], ToInt32(Decimal.Round(value % range * 100)));
}
public static mixColors(Color colorA, Color colorB, int percentage)
{
//combine colors here based on percentage
//I'm to lazy to code this :P
}
Color has the properties R, G, and B. You can take your input value, divide it by 100 to get the bottom color (clipping it at 3). Add one to that to get the top color. Then grab R, G, and B from the bottom and top colors, create a weighted average of each based on value % 100, and make a new Color with those values.

Categories

Resources