I'm trying to use MS Chart with custom controls. My purpose is to:
Highlight only a segment of the line that connects two neighboring points on mouse hover over that piece
Find indexes of those two neighboring points (I need that for being able to drag that line by moving two points simultaneously)
Kind of illustration:
For now I can detect a hover over a line on the chart by using the approach described here. But I'm stuck in finding indexes or at least coordinates of those two points.
So the original idea from this question was to find nearest points by x (assuming that all the series has x-values are indeed steadily increasing) and then calculate y-value. But I have a little improved that and added support for completely vertical lines. So here is my code for capturing the needed line:
private static GrippedLine? LineHitTest(Series series, double xPos, double yPos, Axis xAxis, Axis yAxis)
{
double xPixelPos = xAxis.PixelPositionToValue(xPos);
double yPixelPos = yAxis.PixelPositionToValue(yPos);
DataPoint[] neighbors = new DataPoint[2];
neighbors[0] = series.Points.Last(x => x.XValue <= xPixelPos);
neighbors[1] = series.Points.First(x => x.XValue >= xPixelPos);
DataPoint[] verticalMates;
foreach (DataPoint neighbor in neighbors)
{
if (Math.Abs(neighbor.XValue - xPixelPos) < LINE_GRIP_REGION)
{
verticalMates = series.Points.FindAllByValue(neighbor.XValue, "X").ToArray();
if (verticalMates.Length > 1)
{
if (verticalMates.Length > 2)
{
if (verticalMates[0].YValues[0] < verticalMates[verticalMates.Length - 1].YValues[0])
{
neighbors[0] = verticalMates.LastOrDefault(y => y.YValues[0] < yPixelPos);
neighbors[1] = verticalMates.FirstOrDefault(y => y.YValues[0] >= yPixelPos);
}
else
{
neighbors[0] = verticalMates.LastOrDefault(y => y.YValues[0] > yPixelPos);
neighbors[1] = verticalMates.FirstOrDefault(y => y.YValues[0] <= yPixelPos);
}
}
else
{
neighbors[0] = verticalMates[0];
neighbors[1] = verticalMates[1];
}
break;
}
}
}
double x0 = xAxis.ValueToPixelPosition(neighbors[0].XValue);
double y0 = yAxis.ValueToPixelPosition(neighbors[0].YValues[0]);
double x1 = xAxis.ValueToPixelPosition(neighbors[1].XValue);
double y1 = yAxis.ValueToPixelPosition(neighbors[1].YValues[0]);
double Yinterpolated = y0 + (y1 - y0) * (xPos - x0) / (x1 - x0);
int[] linePoints = new int[2];
// if mouse Y position is near the calculated OR the line is vertical
if (Math.Abs(Yinterpolated - yPos) < LINE_GRIP_REGION || neighbors[0].XValue == neighbors[1].XValue)
{
linePoints[0] = series.Points.IndexOf(neighbors[0]);
linePoints[1] = series.Points.IndexOf(neighbors[1]);
}
else
{
return null;
}
return new GrippedLine()
{
startLinePointIndex = linePoints[0],
endLinePointIndex = linePoints[1],
x0Correction = neighbors[0].XValue - xPixelPos,
y0Correction = neighbors[0].YValues[0] - yPixelPos,
x1Correction = neighbors[1].XValue - xPixelPos,
y1Correction = neighbors[1].YValues[0] - yPixelPos
};
}
I am making a program where I want to take an image and reduce its color palette to a preset palette of 60 colors, and then add a dithering effect. This seems to involve two things:
A color distance algorithm that goes through each pixel, gets its color, and then changes it to the color closest to it in the palette so that that image doesn't have colors that are not contained in the palette.
A dithering algorithm that goes through the color of each pixel and diffuses the difference between the original color and the new palette color chosen across the surrounding pixels.
After reading about color difference, I figured I would use either the CIE94 or CIEDE2000 algorithm for finding the closest color from my list. I also decided to use the fairly common Floyd–Steinberg dithering algorithm for the dithering effect.
Over the past 2 days I have written my own versions of these algorithms, pulled other versions of them from examples on the internet, tried them both first in Java and now C#, and pretty much every single time the output image has the same issue. Some parts of it look perfectly fine, have the correct colors, and are dithered properly, but then other parts (and sometimes the entire image) end up way too bright, are completely white, or all blur together. Usually darker images or darker parts of images turn out fine, but any part that is bright or has lighter colors at all gets turned up way brighter. Here is an example of an input and output image with these issues:
Input:
]3
Output:
I do have one idea for what may be causing this. When a pixel is sent through the "nearest color" function, I have it output its RGB values and it seems like some of them have their R value (and potentially other values??) pushed way higher than they should be, and even sometimes over 255 as shown in the screenshot. This does NOT happen for the earliest pixels in the image, only for ones that are multiple pixels in already and are already somewhat bright. This leads me to believe it is the dithering/error algorithm doing this, and not the color conversion or color difference algorithms. If that is the issue, then how would I go about fixing that?
Here's the relevant code and functions I'm using. At this point it's a mix of stuff I wrote and stuff I've found in libraries or other StackOverflow posts. I believe the main dithering algorithm and C3 class are copied basically directly from this Github page (and changed to work with C#, obviously)
class Program
{
public static C3[] palette = new C3[]{
new C3(196, 76, 86),
new C3(186, 11, 39),
new C3(113, 0, 32),
new C3(120, 41, 56),
new C3(203, 125, 84),
new C3(205, 90, 40),
new C3(175, 50, 33),
new C3(121, 61, 54),
// etc... palette is 60 colors total
// each object contains an r, g, and b value
};
static void Main(string[] args)
{
// paths for original image and output path for dithered image
string path = #"C:\Users\BillehBawb\Desktop\";
string imgPath = path + "amy.jpg";
string createPath = path + "amydithered.jpg";
// pulls the original image, runs the dithering function, then saves the new image
Bitmap img = new Bitmap(imgPath);
Bitmap dithered = floydSteinbergDithering(img);
dithered.Save(createPath, ImageFormat.Jpeg);
}
// loops through every pixel in the image, populates a 2d array with the pixel colors, then loops through again to change the color to one in the palette and do the dithering algorithm
private static Bitmap floydSteinbergDithering(Bitmap img)
{
int w = img.Width;
int h = img.Height;
C3[,] d = new C3[h, w];
for (int y = 0; y < h; y++)
{
for (int x = 0; x < w; x++)
{
d[y, x] = new C3(img.GetPixel(x, y).ToArgb());
}
}
for (int y = 0; y < img.Height; y++)
{
for (int x = 0; x < img.Width; x++)
{
C3 oldColor = d[y, x];
C3 newColor = findClosestPaletteColor(oldColor, palette);
img.SetPixel(x, y, newColor.toColor());
C3 err = oldColor.sub(newColor);
if (x + 1 < w)
{
d[y, x + 1] = d[y, x + 1].add(err.mul(7.0 / 16));
}
if (x - 1 >= 0 && y + 1 < h)
{
d[y + 1, x - 1] = d[y + 1, x - 1].add(err.mul(3.0 / 16));
}
if (y + 1 < h)
{
d[y + 1, x] = d[y + 1, x].add(err.mul(5.0 / 16));
}
if (x + 1 < w && y + 1 < h)
{
d[y + 1, x + 1] = d[y + 1, x + 1].add(err.mul(1.0 / 16));
}
}
}
return img;
}
// loops through the palette, converts the input pixel and palette colors to the LAB format, finds the difference between all of them, and selects the palette color with the lowest difference
private static C3 findClosestPaletteColor(C3 c, C3[] palette)
{
double[] pixelLab = rgbToLab(c.toColor().R, c.toColor().G, c.toColor().B);
double minDist = Double.MaxValue;
int colorIndex = 0;
for (int i = 0; i < palette.Length; i++)
{
double[] colors = rgbToLab(palette[i].toColor().R, palette[i].toColor().G, palette[i].toColor().B);
double dist = labDist(pixelLab[0], pixelLab[1], pixelLab[2], colors[0], colors[1], colors[2]);
if (dist < minDist)
{
colorIndex = i;
minDist = dist;
}
}
return palette[colorIndex];
}
// finds the deltaE/difference between two sets of LAB colors with the CIE94 algorithm
public static double labDist(double l1, double a1, double b1, double l2, double a2, double b2)
{
var deltaL = l1 - l2;
var deltaA = a1 - a2;
var deltaB = b1 - b2;
var c1 = Math.Sqrt(Math.Pow(a1, 2) + Math.Pow(b1, 2));
var c2 = Math.Sqrt(Math.Pow(a2, 2) + Math.Pow(b2, 2));
var deltaC = c1 - c2;
var deltaH = Math.Pow(deltaA, 2) + Math.Pow(deltaB, 2) - Math.Pow(deltaC, 2);
deltaH = deltaH < 0 ? 0 : Math.Sqrt(deltaH);
double sl = 1.0;
double kc = 1.0;
double kh = 1.0;
double Kl = 1.0;
double K1 = .045;
double K2 = .015;
var sc = 1.0 + K1 * c1;
var sh = 1.0 + K2 * c1;
var i = Math.Pow(deltaL / (Kl * sl), 2) +
Math.Pow(deltaC / (kc * sc), 2) +
Math.Pow(deltaH / (kh * sh), 2);
var finalResult = i < 0 ? 0 : Math.Sqrt(i);
return finalResult;
}
// converts RGB colors to the XYZ and then LAB format so the color difference algorithm can be done
public static double[] rgbToLab(int R, int G, int B)
{
float[] xyz = new float[3];
float[] lab = new float[3];
float[] rgb = new float[3];
rgb[0] = R / 255.0f;
rgb[1] = G / 255.0f;
rgb[2] = B / 255.0f;
if (rgb[0] > .04045f)
{
rgb[0] = (float)Math.Pow((rgb[0] + .055) / 1.055, 2.4);
}
else
{
rgb[0] = rgb[0] / 12.92f;
}
if (rgb[1] > .04045f)
{
rgb[1] = (float)Math.Pow((rgb[1] + .055) / 1.055, 2.4);
}
else
{
rgb[1] = rgb[1] / 12.92f;
}
if (rgb[2] > .04045f)
{
rgb[2] = (float)Math.Pow((rgb[2] + .055) / 1.055, 2.4);
}
else
{
rgb[2] = rgb[2] / 12.92f;
}
rgb[0] = rgb[0] * 100.0f;
rgb[1] = rgb[1] * 100.0f;
rgb[2] = rgb[2] * 100.0f;
xyz[0] = ((rgb[0] * .412453f) + (rgb[1] * .357580f) + (rgb[2] * .180423f));
xyz[1] = ((rgb[0] * .212671f) + (rgb[1] * .715160f) + (rgb[2] * .072169f));
xyz[2] = ((rgb[0] * .019334f) + (rgb[1] * .119193f) + (rgb[2] * .950227f));
xyz[0] = xyz[0] / 95.047f;
xyz[1] = xyz[1] / 100.0f;
xyz[2] = xyz[2] / 108.883f;
if (xyz[0] > .008856f)
{
xyz[0] = (float)Math.Pow(xyz[0], (1.0 / 3.0));
}
else
{
xyz[0] = (xyz[0] * 7.787f) + (16.0f / 116.0f);
}
if (xyz[1] > .008856f)
{
xyz[1] = (float)Math.Pow(xyz[1], 1.0 / 3.0);
}
else
{
xyz[1] = (xyz[1] * 7.787f) + (16.0f / 116.0f);
}
if (xyz[2] > .008856f)
{
xyz[2] = (float)Math.Pow(xyz[2], 1.0 / 3.0);
}
else
{
xyz[2] = (xyz[2] * 7.787f) + (16.0f / 116.0f);
}
lab[0] = (116.0f * xyz[1]) - 16.0f;
lab[1] = 500.0f * (xyz[0] - xyz[1]);
lab[2] = 200.0f * (xyz[1] - xyz[2]);
return new double[] { lab[0], lab[1], lab[2] };
}
}
And here is the C3 class, its basically just the Color class with some math functions to make it cleaner to do the dithering on
class C3
{
int r, g, b;
public C3(int c)
{
Color color = Color.FromArgb(c);
r = color.R;
g = color.G;
b = color.B;
}
public C3(int r, int g, int b)
{
this.r = r;
this.g = g;
this.b = b;
}
public C3 add(C3 o)
{
return new C3(r + o.r, g + o.g, b + o.b);
}
public int clamp(int c)
{
return Math.Max(0, Math.Min(255, c));
}
public int diff(C3 o)
{
int Rdiff = o.r - r;
int Gdiff = o.g - g;
int Bdiff = o.b - b;
int distanceSquared = Rdiff * Rdiff + Gdiff * Gdiff + Bdiff * Bdiff;
return distanceSquared;
}
public C3 mul(double d)
{
return new C3((int)(d * r), (int)(d * g), (int)(d * b));
}
public C3 sub(C3 o)
{
return new C3(r - o.r, g - o.g, b - o.b);
}
public Color toColor()
{
return Color.FromArgb(clamp(r), clamp(g), clamp(b));
}
public int toRGB()
{
return toColor().ToArgb();
}
}
Sorry for the massive code dump, the functions are just pretty large and I want to provide everything I can. If anyone has any suggestions on a simpler or different way to do this, or if you know how to fix the issue I'm running into, please let me know. I have tried quite a few different algorithms for my desired result but I haven't been able to get any of them to do what I want them to. Any help or ideas are greatly appreciated, thank you!
It appears that when you shift the error to the neighbors in floydSteinbergDithering() the r,g,b values never get clamped until you cast them back to Color.
Since you're using int and not byte there is no prevention of overflows to negative or large values greater than 255 for r, g, and b.
You should consider implementing r,g, and b as properties that clamp to 0-255 when they're set.
This will ensure their values will never be outside your expected range (0 - 255).
class C3
{
private int r;
public int R
{
get => r;
set
{
r = clamp(value);
}
}
private int g;
public int G
{
get => g;
set
{
g = clamp(value);
}
}
private int b;
public int B
{
get => b;
set
{
b = clamp(value);
}
}
// rest of class
}
Project: intelligence scissors.
The first part of the project is to load an image (in the form of RGBPixel 2d array) then to construct a graph of weights to use it later to determine the shortest path between 2 points on the image (the 2 points will be determined by an anchor and a free point..In short i will have the source and the destination points).
I have a function that open the image and return a RGBPixel 2d array already made.Now image is loaded i want to construct the graph to do the i should use a function called CalculatePixelEnergies here is the code
public static Vector2D CalculatePixelEnergies(int x, int y, RGBPixel[,] ImageMatrix)
{
if (ImageMatrix == null) throw new Exception("image is not set!");
Vector2D gradient = CalculateGradientAtPixel(x, y, ImageMatrix);
double gradientMagnitude = Math.Sqrt(gradient.X * gradient.X + gradient.Y * gradient.Y);
double edgeAngle = Math.Atan2(gradient.Y, gradient.X);
double rotatedEdgeAngle = edgeAngle + Math.PI / 2.0;
Vector2D energy = new Vector2D();
energy.X = Math.Abs(gradientMagnitude * Math.Cos(rotatedEdgeAngle));
energy.Y = Math.Abs(gradientMagnitude * Math.Sin(rotatedEdgeAngle));
return energy;
}
This function use CalculateGradientAtPixel, Here is the code in case you want it.
private static Vector2D CalculateGradientAtPixel(int x, int y, RGBPixel[,] ImageMatrix)
{
Vector2D gradient = new Vector2D();
RGBPixel mainPixel = ImageMatrix[y, x];
double pixelGrayVal = 0.21 * mainPixel.red + 0.72 * mainPixel.green + 0.07 * mainPixel.blue;
if (y == GetHeight(ImageMatrix) - 1)
{
//boundary pixel.
for (int i = 0; i < 3; i++)
{
gradient.Y = 0;
}
}
else
{
RGBPixel downPixel = ImageMatrix[y + 1, x];
double downPixelGrayVal = 0.21 * downPixel.red + 0.72 * downPixel.green + 0.07 * downPixel.blue;
gradient.Y = pixelGrayVal - downPixelGrayVal;
}
if (x == GetWidth(ImageMatrix) - 1)
{
//boundary pixel.
gradient.X = 0;
}
else
{
RGBPixel rightPixel = ImageMatrix[y, x + 1];
double rightPixelGrayVal = 0.21 * rightPixel.red + 0.72 * rightPixel.green + 0.07 * rightPixel.blue;
gradient.X = pixelGrayVal - rightPixelGrayVal;
}
return gradient;
}
In my code of graph construction i decided to make a 2d double array to hold the weights, here what i do but it seems to be a wrong construction
public static double [,] calculateWeights(RGBPixel[,] ImageMatrix)
{
double[,] weights = new double[1000, 1000];
int height = ImageOperations.GetHeight(ImageMatrix);
int width = ImageOperations.GetWidth(ImageMatrix);
for (int y = 0; y < height - 1; y++)
{
for (int x = 0; x < width - 1; x++)
{
Vector2D e;
e = ImageOperations.CalculatePixelEnergies(x, y, ImageMatrix);
weights[y + 1, x] = 1 / e.X;
weights[y, x + 1] = 1 / e.Y;
}
}
return weights;
}
an example for an image
an other example for an image
Lately I've been working with OxyPlot library doing some charts. I was wondering if I can have a heatmap with comments for each one of the rectangles. looking forward to an answer.
I finally found that I needed to make a new class and extend the heatmap series and override the RenderLabels method in order to change its functionality.
protected override void RenderLabels(IRenderContext rc, OxyRect rect)
{
var clip = this.GetClippingRect();
int m = this.Data.GetLength(0);
int n = this.Data.GetLength(1);
double fontSize = (rect.Height / n) * this.LabelFontSize;
double left = this.X0;
double right = this.X1;
double bottom = this.Y0;
double top = this.Y1;
var s00 = this.Orientate(this.Transform(left, bottom)); // disorientate
var s11 = this.Orientate(this.Transform(right, top)); // disorientate
double sdx = (s11.X - s00.X) / (m - 1);
double sdy = (s11.Y - s00.Y) / (n - 1);
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n; j++)
{
var point = this.Orientate(new ScreenPoint(s00.X + (i * sdx), s00.Y + (j * sdy))); // re-orientate
var v = GetValue(this.Data, i, j);
var color = this.ColorAxis.GetColor(v);
var hsv = color.ToHsv();
var textColor = hsv[2] > 0.6 ? OxyColors.Black : OxyColors.White;
var label = this.Labels[i, j];
rc.DrawClippedText(
clip,
point,
label,
textColor,
this.ActualFont,
fontSize,
500,
0,
HorizontalAlignment.Center,
VerticalAlignment.Middle);
}
}
}
Currently i'm trying to produce a simple 2D map generation program, and it is pretty much finished apart from one key thing; The movement of the generated islands. The way the program functions it keeps all the islands in the middle of the map separated by colour like in some like disco ball of puke thing, but my main problem is trying to move the islands into new locations.
The program should randomly place the islands in new places based on colour, but i am having a considerable amount of difficulty doing this, as all solutions i have attempted have either fell on their face in a tsunami of 'index out of bounds of the array' errors or have worked, but taken literal hours to move a single island.
TLDR; Do any algorithms exist that would potentially allow me to move shapes made of pixels to random locations while keeping their existing shapes? mine suck.
Edit: I will try and rewrite this to be easier to read later since i'm in a rush, but in essence it reads all the pixels from the circle using .getpixel and stores them in an array based on their colour, it then generates a random location and runs the same code again, only this time it will accept a colour as an argument and will place the colour at the pixel relative to the centre of the circle if it finds a colour that is the same as the colour it is currently accepting.
In theory this should go through every colour and generate a new position for each one that maintains the shape of the island upon generation, but in practice it just takes forever.
//Thoughts - use the circle generator as a radar to find all the seperate colors, then for each color randomly generate an x and a y. then use the circle generator but only apply the colors that are selected
if (tempi >= 716 || tempib > 0)
{
if(tempib <= 0)
{
tempi = 0;
tempib = 1;
randxb = Rander.Next(10, xlen - 10);
randyb = Rander.Next(10, ylen - 10);
}
tempi += 1;
day += 1;
if(day >= 31)
{
month += 1;
day = 1;
}
if(month >= 13)
{
year += 1;
month = 1;
}
AD = "PF";
era = "Prehistoric era";
age = "Islandic Age";
Point temppb = new Point(randxb, randyb);
if (colours[tempib] == Color.DarkBlue || colours[tempib] == Color.FromArgb(0, 0, 0))
{
tempib += 1;
}
else
{
Radar(0, temppb, "write", colours[tempib]);
}
tempi = 0;
tempib += 1;
randxb = Rander.Next(10, xlen - 10);
randyb = Rander.Next(10, ylen - 10);
if (tempib >= islandnuma)
{
age = "Neanderthalic Age";
}
}
else
{
year += Rander.Next(1, 3);
day = 1;
AD = "PF";
era = "Prehistoric era";
Point tempp = new Point(xlen / 2 - 150, ylen / 2 - 150);
tempi += 1;
Radar(tempi, tempp, "scan", Color.White);
if(tempi >= 716)
{
clearmap();
}
}
}
This is the terrible algorithm it calls
Color[,] scanresults = new Color[717, 4499]; //shell, place in shell
private void Radar(int circle, Point pos, string mode, Color col) //Fuck this doesnt work i need to change it
{
using (var g = Graphics.FromImage(pictureBox1.Image))
{
if (mode == "scan")
{
int mj = 0;
if (circle <= 716)
{
for (double i = 0.0; i < 360.0; i += 0.1)
{
mj += 1;
int radius = circle / 2; //max size = 716
double angle = i * System.Math.PI / 180;
int x = pos.X - (int)(radius * System.Math.Cos(angle));
int y = pos.Y - (int)(radius * System.Math.Sin(angle));
Color m = Map.GetPixel(x, y);
scanresults[circle, mj] = Map.GetPixel(x, y);
}
}
else
{
return;
}
}
else
{
if(mode == "write")
{
for(int c2 = 0; c2 <= 716; c2++)
{
int bmj = 0;
for (double i = 0.0; i < 360.0; i += 0.1)
{
try
{
if (mode == "write")
{
bmj += 1;
int radius = (716 - c2) / 2; //max size = 716
double angle = i * System.Math.PI / 180;
int x = pos.X - (int)(radius * System.Math.Cos(angle));
int y = pos.Y - (int)(radius * System.Math.Sin(angle));
if (scanresults[c2, bmj] == col)
{
Map.SetPixel(x, y, col);
}
}
}
catch (Exception em)
{
Console.Write("error: " + em);
}
//Color m = Map.GetPixel(x, y);
//scanresults[circle, mj] = Map.GetPixel(x, y);
}
}
}
}
//Dont hate me im defensive about my terrible coding style
}
}