Graphic transformations - c#

I'm working with data (stuff like Sin/Cosin waves, etc) that repeat with frequency M.
I've written a simple display control where it takes the data and paints connected lines to represent the data in a pretty picture.
My question is, the data is given where if painted onto a bitmap, quadrant 1 data is in quadrant 3 and quadrant 2 data is in quadrant 4 (and vice versa).
The bitmap is of width M and hight array.Max - array.Min.
Is there a simple transform for changing the data so it will display in the appropriate quadrants?

A good way of thinking about it is that (0,0) in world coordinates is divided between
(0,0), (width, 0), (0,height), (width, height)
which would (width/2, height/2) in image coordinates.
From there, the transform would be:
Data(x,y) => x = ABS(x - (width/2)), y = ABS(y - (Height/2))

Graphics.ScaleTransform is not a good idea because it will affect not only layout but also drawing itself (thickness of strokes, texts and so on).
I suggest you to prepare points list and then perform a transformation to them using the Matrix class. This is a small example I made for you, hope it will be helpful.
private PointF[] sourcePoints = GenerateFunctionPoints();
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.Clear(Color.Black);
// Wee need to perform transformation on a copy of a points array.
PointF[] points = (PointF[])sourcePoints.Clone();
// The way to calculate width and height of our drawing.
// Of course this operation may be performed outside this method for better performance.
float drawingWidth = points.Max(p => p.X) - points.Min(p => p.X);
float drawingHeight = points.Max(p => p.Y) - points.Min(p => p.Y);
// Calculate the scale aspect we need to apply to points.
float scaleAspect = Math.Min(ClientSize.Width / drawingWidth, ClientSize.Height / drawingHeight);
// This matrix transofrmation allow us to scale and translate points so the (0,0) point will be
// in the center of the screen. X and Y axis will be scaled to fit the drawing on the screen.
// Also the Y axis will be inverted.
Matrix matrix = new Matrix();
matrix.Scale(scaleAspect, -scaleAspect);
matrix.Translate(drawingWidth / 2, -drawingHeight / 2);
// Perform a transformation and draw curve using out points.
matrix.TransformPoints(points);
e.Graphics.DrawCurve(Pens.Green, points);
}
private static PointF[] GenerateFunctionPoints()
{
List<PointF> result = new List<PointF>();
for (double x = -Math.PI; x < Math.PI; x = x + 0.1)
{
double y = Math.Sin(x);
result.Add(new PointF((float)x, (float)y));
}
return result.ToArray();
}
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
Invalidate();
}

Try to invert the y-axis using
g.ScaleTransform(1, -1);

Also remember that for drawing in a scaled context, if you have to, Pen for example takes width as Single in some of its constructors, meaning inversely proportional fractional values can be used to make an invariant compensation for the effect of ScaleTransform.
UPDATE: forget that, Pen has its own local ScaleTransform, so both x an y can be compensated for.

Related

Issue with trigonometry using Xamarin.Forms.Map.Position

I am working on a mobile app in C# using the Xamarin framework. I am trying to move a point by a fixed angle on a map like in the first part of the gif below. I believe I am using the right mathematical functions to compute the coordinates of the shifted points since in first part of the GIF, in GeoGebra, everything seems to be fine.
But when it comes to the actual in-app implementation, the results are quite weird : the angle is not consistent and the distance between the center and the points varies by moving the target.
The GIF showing the issue
I don't have a clue about what is wrong with the code. In the code below I use polylineOptions to draw the lines but I've tried with a Polygon and it displays the same results. Maybe it's because customMap.UserPin.Position returns the coordinates in Decimal Degree format (i.g. 34.00462, -4.512221) and the gap between two position is too small for a double.
Here are the two functions used to draw the lines.
// Add a cone's side to the variable coneLines
private void addConePolyline(double angle, CustomMap customMap, LatLng userPos)
{
// The coordinates of the end of the side to be drawn
LatLng conePoint = movePoint(angle, customMap.UserPin.Position, customMap.TargetPin.Position);
var polylineOptions = new PolylineOptions();
polylineOptions.InvokeWidth(10f);
polylineOptions.InvokeColor(Android.Graphics.Color.Argb(240, 255, 20, 147)); // Pink
polylineOptions.Add(userPos);
polylineOptions.Add(conePoint);
// Add the line to coneLines
coneLines.Add(map.AddPolyline(polylineOptions));
}
// Moves a point by the given angle on a circle of center rotationCenter with respect to p
private LatLng movePoint(double angle, Position rotationCenter, Position initialPoint)
{
// Compute the components of the translation vector between rotationCenter and initialPoint
double dx = initialPoint.Latitude - rotationCenter.Latitude;
double dy = initialPoint.Longitude - rotationCenter.Longitude;
// Compute the moved point's position
double x = rotationCenter.Latitude + Math.Cos(angle) * dx - Math.Sin(angle) * dy;
double y = rotationCenter.Longitude + Math.Sin(angle) * dx + Math.Cos(angle) * dy;
LatLng res = new LatLng(x, y);
return res;
}
I hope someone can help me with this!
Thank you.

2D Rotated rectangle contains point calculation

My issue is that I've been trying to check if a rectangle that is rotated by a certain amount of degrees contain a point, but I wasn't able to calculate that after many attempts with the help of some code samples and examples that I've found online.
What I got is the rectangle (X, Y, Width, Height, Rotation) and the point (X, Y) and I've been trying to create a simple function that lets me instantly calculate that, which would be something something like this:
public bool Contains(Rect Rectangle, float RectangleRotation, Point PointToCheck);
But as I mentioned, I wasn't able to do so, those mathematical calculations that include some formulas I found online are way too much for me to understand.
Could someone help me with calculating this? If you could provide the calculation in C# code form (not formulas) then that would be great! Thanks.
PS: Using the 2D Physics Engine that is available in Unity3D is not a option, my rectangle is not associated with a gameobject that I could attach a 2D collision component to, I need to do this mathematically without the involvement of gameobjects or components.
Edit: I forgot to mention, the rectangle is being rotated by the middle of the rectangle (center/origin).
Rather than checking if the point is in a rotated rectangle, just apply the opposite of the rotation to the point and check if the point is in a normal rectangle. In other words, change your perspective by rotating everything by -RectangleRotation, so that the rectangle does not appear rotated at all.
public bool Contains(Rect rect, float rectAngle, Point point)
{
// rotate around rectangle center by -rectAngle
var s = Math.Sin(-rectAngle);
var c = Math.Cos(-rectAngle);
// set origin to rect center
var newPoint = point - rect.center;
// rotate
newPoint = new Point(newPoint.x * c - newPoint.y * s, newPoint.x * s + newPoint.y * c);
// put origin back
newPoint = newPoint + rect.center;
// check if our transformed point is in the rectangle, which is no longer
// rotated relative to the point
return newPoint.x >= rect.xMin && newPoint.x <= rect.xMax && newPoint.y >= rect.yMin && newPoint.y <= rect.yMax;
}

How does one set an image as or along a chart axis?

I am trying to use a colored spectrum strip as an axis for a chart. The idea is to match the color on the image with its associated wavelength along the x-axis at the bottom. The strip needs to change in size to match changes of the chart area and expand and contract sections to match scroll-zooming in the chart area.
I have tried using image annotations but as the chart area changes, the annotation dimensions remain fixed. Also, the scroll zooming that focuses in on mouse position obviously has no effect on the annotation.
The approach that came closest was using the image as a background for the chart area. This automatically scaled the image as the chart area changed but scroll-zooming has no effect on the background image. Also, it would be ideal to have the background clear so as to avoid obscuring data plot points. I can edit the image to have a large transparent section and only a colored strip at the bottom but even then, that strip could obscure lower intensity data points.
Spectrum as annotation and background:
Annotation not scaling, background scales well:
Both annotation and background not scaling with zooming:
This is a nice idea.
The simplest way is to draw the image in a Paint event of the Chart, maybe PrePaint.
Let's go to work.. We will use the DrawImage overload that allows us zooming as well as cropping. For this we need two rectangles.
The first challenge is to always get the correct target rectangle.
For this we need to convert the InnerPlotPosition from relative positions to absolute pixels.
These two functions will help:
RectangleF ChartAreaClientRectangle(Chart chart, ChartArea CA)
{
RectangleF CAR = CA.Position.ToRectangleF();
float pw = chart.ClientSize.Width / 100f;
float ph = chart.ClientSize.Height / 100f;
return new RectangleF(pw * CAR.X, ph * CAR.Y, pw * CAR.Width, ph * CAR.Height);
}
RectangleF InnerPlotPositionClientRectangle(Chart chart, ChartArea CA)
{
RectangleF IPP = CA.InnerPlotPosition.ToRectangleF();
RectangleF CArp = ChartAreaClientRectangle(chart, CA);
float pw = CArp.Width / 100f;
float ph = CArp.Height / 100f;
return new RectangleF(CArp.X + pw * IPP.X, CArp.Y + ph * IPP.Y,
pw * IPP.Width, ph * IPP.Height);
}
With these numbers setting the destination rectangle is as simple as:
Rectangle tgtR = Rectangle.Round(new RectangleF(ipr.Left, ipr.Bottom - 15, ipr.Width, 15));
You can chose a height as you like..
The next challenge is the source rectangle.
Without zooming it would simply be:
Rectangle srcR = new Rectangle( 0, 0, bmp.Width, bmp.Height);
But for zooming and panning we need to scale it; for this we can use the x-axis and the ScaleView's Minimum and Maximum values.
We calculate factors for the first and last spot on the axis:
double f1 = ax.ScaleView.ViewMinimum / (ax.Maximum - ax.Minimum);
double f2 = ax.ScaleView.ViewMaximum / (ax.Maximum - ax.Minimum);
now we get the source rectangle maybe like this:
int x = (int)(bmp.Width * f1);
int xx = (int)(bmp.Width * f2);
Rectangle srcR = new Rectangle( x, 0, xx - x, bmp.Height);
Let's put it together:
private void chart_PrePaint(object sender, ChartPaintEventArgs e)
{
// a few short names
Graphics g = e.ChartGraphics.Graphics;
ChartArea ca = chart.ChartAreas[0];
Axis ax = ca.AxisX;
// pixels of plot area
RectangleF ipr = InnerPlotPositionClientRectangle(chart, ca);
// scaled first and last position
double f1 = ax.ScaleView.ViewMinimum / (ax.Maximum - ax.Minimum);
double f2 = ax.ScaleView.ViewMaximum / (ax.Maximum - ax.Minimum);
// actual drawing with the zooming overload
using (Bitmap bmp = (Bitmap)Bitmap.FromFile(imagePath))
{
int x = (int)(bmp.Width * f1);
int xx = (int)(bmp.Width * f2);
Rectangle srcR = new Rectangle( x, 0, xx - x, bmp.Height);
Rectangle tgtR = Rectangle.Round(
new RectangleF(ipr.Left , ipr.Bottom - 15, ipr.Width, 15));
g.DrawImage(bmp, tgtR, srcR, GraphicsUnit.Pixel);
}
}
A few notes:
Of course I would recomend to use an Image resource instead of always loading from disk!
The Drawing will always overlay the data points and also the grids. You can either..
choose a different minimum to make room
make the image smaller
move it below the x-axis labels
make the image semi-transparent
make the x-axis so fat that it can hold the image strip : ax.LineWidth = 10
For the latter solution you would want to offset the y-position depending on the zoom state. Quick and dirty: int yoff = (ax.ScaleView.IsZoomed ? 12 : 5);. To avoid black stripes also make the axis Transparent or chart.BackColor..
Update:
You can also revert to using a StripLine. It can scale its BackgroundImage and you would have to create a suitable image whenever changing the scaleview, i.e. when zooming or panning. For this much of the above code would be used to create the new images. See this post for examples of adding and replacing varying NamedImage to a Chart! (The relevant portion is close to the end about the marker images!)
In fact I found that way to be the best solution and have added a second answer.
Alternative and recommended solution:
I dabbled with the last option I mentioned in my other answer and found it to be rather nice; it is similarily extensive, so I decided to post a second answer.
The idea is to use a StripLine with just the right BackgroundImage.
The advantage is that is will display nicely under all chart elements and never draw over the axis, grid, datapoints or conflict with the zoom tools.
Since the StripLine must be updated repeatedly I put it in a function:
Here is the function; it makes use of the same two helper functions to calculate pixel positions as the other answer does..:
void updateStripLine(Chart chart, ChartArea ca, string name)
{
// find our stripline; one could pass in a class level variable as well
StripLine sl = ca.AxisY.StripLines.Cast<StripLine>()
.Where(s => s.Tag.ToString() == name).FirstOrDefault();
if (sl != null) // either clean-up the resources..
{
var oldni = chart.Images.FindByName(name);
if (oldni != null)
{
oldni.Image.Dispose();
chart.Images.Remove(oldni);
oldni.Dispose();
}
}
else // or, create the line
{
sl = new StripLine();
sl.Tag = name;
ca.AxisY.StripLines.Add(sl);
}
ca.RecalculateAxesScale();
RectangleF ipr = InnerPlotPositionClientRectangle(chart, ca);
Axis ax = ca.AxisX;
Axis ay = ca.AxisY;
double f1 = ax.ScaleView.ViewMinimum / (ax.Maximum - ax.Minimum);
double f2 = ax.ScaleView.ViewMaximum / (ax.Maximum - ax.Minimum);
Bitmap b0 = (Bitmap)chart.Images["spectrum"].Image;
int x = (int)(b0.Width * f1);
int xx = (int)(b0.Width * f2);
Rectangle srcR = new Rectangle( x, 0, xx - x, b0.Height);
Rectangle tgtR = Rectangle.Round(new RectangleF(0,0, ipr.Width , 10));
// create bitmap and namedImage:
Bitmap bmp = new Bitmap( tgtR.Width, tgtR.Height);
using (Graphics g = Graphics.FromImage(bmp))
{ g.DrawImage(b0, tgtR, srcR, GraphicsUnit.Pixel); }
NamedImage ni = new NamedImage(name, bmp);
chart.Images.Add(ni);
sl.BackImageWrapMode = ChartImageWrapMode.Scaled;
sl.StripWidth = ay.PixelPositionToValue(0) - ay.PixelPositionToValue(12);
sl.Interval = 100; // make large enough to avoid another sLine showing up
sl.IntervalOffset = 0;
sl.BackImage = name;
}
Much of the comments and links apply, especially wrt to the NamedImage we use for the StripLine.
A few more notes:
I use one of the (four) axis conversion functions, PixelPositionToValue to calculate a pixel height of 12px; the StripLine takes values, so I use two pixel values to get the right difference value.
To identify the StripLine I use the Tag property. Of course the Name property would be much more natural, but it is read-only. No idea why?!
The function is called from the AxisViewChanged, the Resize event and also the the PrePaint event; this makes sure it will always be called when needed. To avoid invalid calls from the PrePaint there I do it like this: if (ay.StripLines.Count == 0) updateStripLine(chart, ca, "sl"); Of course you should adapt if you use other StripLines on this axis..
The code makes use of the same image as before; but I have put it into a first NamedImage called spectrum. This would be an option in the 1st answer as well.
NamedImage spectrum = new NamedImage("spectrum", Bitmap.FromFile(imagePath);
chart.Images.Add(spectrum);
It also makes sure to dispose of the old images properly, I hope..

find the center point of coordinate 2d array c#

Is there a formula to average all the x, y coordinates and find the location in the dead center of them.
I have 100x100 squares and inside them are large clumps of 1x1 red and black points, I want to determine out of the red points which one is in the middle.
I looked into line of best fit formulas but I am not sure if this is what I need.
Sometimes all the red will be on one side, or the other side. I want to essentially draw a line then find the center point of that line, or just find the center point of the red squares only. based on the 100x100 grid.
List<Point> dots = new List<Point>();
int totalX = 0, totalY = 0;
foreach (Point p in dots)
{
totalX += p.X;
totalY += p.Y;
}
int centerX = totalX / dots.Count;
int centerY = totalY / dots.Count;
Simply average separately the x coordinates and the y coordinates, the result will be the coordinates of the "center".
What if there are two or more subsets of red points ? Do you want the black point inside them?
Otherwis, if I understood your question, just give a weight of 1 to red points and 0 to blacks. Then do the weighted mean on X and Y coordinate

Updating four points to stay in a Rectangle shape when one is moved

I am trying to draw a rectangular object that allows the user to click on a corner-point to resize and also rotate the rectangle in a 2D space.
Therefore I am using an array of four points ordered A, B, C, D (or 0, 1, 2, 3) from top-left to bottom-left in clockwise order.
The rotation works fine, I calculate the center point and rotate each point around it by an angle.
The resizing is done by determining which point was pressed down, and then setting its new position to the position of the mouse on each MouseMove event. The two adjacent points then need to be updated to stay in a rectangular shape. The resizing is intermittently not working. I have tried many ways to error-check, but all leave me with the same problem where if I move the mouse back and forth over the opposing point while moving a point, the points get distorted and are no longer a rectangular shape.
SOURCE CODE HERE
https://www.assembla.com/code/moozhe-testing/subversion/nodes/rotateRectangle
EXERPT OF PROBLEM CODE
private void MovePoint(int id, PointF newPoint)
{
PointF oldPoint = points[id];
PointF delta = newPoint.Substract(oldPoint);
PointF pointPrevious = points[(id + 3) % 4];
PointF pointNext = points[(id + 1) % 4];
PointF sidePrevious = pointPrevious.Substract(oldPoint);
PointF sideNext = pointNext.Substract(oldPoint);
PointF previousProjection = Projection(delta, sidePrevious);
PointF nextProjection = Projection(delta, sideNext);
pointNext = pointNext.AddPoints(previousProjection);
pointPrevious = pointPrevious.AddPoints(nextProjection);
points[(id + 3) % 4] = pointPrevious;
points[(id + 1) % 4] = pointNext;
points[id] = newPoint;
}
private PointF Projection(PointF vectorA, PointF vectorB)
{
PointF vectorBUnit = new PointF(vectorB.X, vectorB.Y);
vectorBUnit = vectorBUnit.Normalize();
float dotProduct = vectorA.X * vectorBUnit.X + vectorA.Y * vectorBUnit.Y;
return vectorBUnit.MultiplyByDecimal(dotProduct);
}
It sounds like you might want to be using a transformation matrix, instead of updating X/Y coordinates manually. Please check out this link:
Comparing GDI mapping modes with GDI+ transforms
Here's the MSDN reference:
https://learn.microsoft.com/en-us/dotnet/api/system.drawing.graphics.transform

Categories