Similar to How do I convert from mouse coordinates to pixel coordinates of a TransformedBitmap? but with the added wrinkle that my Image is actually embedded in a larger parent Grid, which has a background, and I would like the pixel coordinates to also be accurate when hovering in the regions beyond the bounds of the image.
Here is my XAML:
<DockPanel>
<Label DockPanel.Dock="Bottom" Name="TheLabel" />
<Grid DockPanel.Dock="Top" Name="TheGrid" Background="Gray" MouseMove="TheGrid_MouseMove">
<Image Name="TheImage" Stretch="Uniform" RenderOptions.BitmapScalingMode="NearestNeighbor" />
</Grid>
</DockPanel>
And here is the code:
public MainWindow()
{
InitializeComponent();
const int WIDTH = 4;
const int HEIGHT = 3;
byte[] pixels = new byte[WIDTH * HEIGHT * 3];
// top-left corner red, bottom-right corner blue for orientation
pixels[0] = Colors.Red.B;
pixels[1] = Colors.Red.G;
pixels[2] = Colors.Red.R;
pixels[(WIDTH * (HEIGHT - 1) + (WIDTH - 1)) * 3 + 0] = Colors.Blue.B;
pixels[(WIDTH * (HEIGHT - 1) + (WIDTH - 1)) * 3 + 1] = Colors.Blue.G;
pixels[(WIDTH * (HEIGHT - 1) + (WIDTH - 1)) * 3 + 2] = Colors.Blue.R;
BitmapSource bs = BitmapSource.Create(WIDTH, HEIGHT, 96.0, 96.0, PixelFormats.Bgr24, null, pixels, WIDTH * 3);
TheImage.Source = new TransformedBitmap(bs, new RotateTransform(90.0));
}
private void TheGrid_MouseMove(object sender, MouseEventArgs e)
{
Point p = TheGrid.TranslatePoint(e.GetPosition(TheGrid), TheImage);
if (TheImage.Source is BitmapSource bs)
{
p = new Point(p.X * bs.PixelWidth / TheImage.ActualWidth, p.Y * bs.PixelHeight / TheImage.ActualHeight);
if (TheImage.Source is TransformedBitmap tb)
{
Matrix inverse = tb.Transform.Value;
inverse.Invert();
inverse.OffsetX = 0.0;
inverse.OffsetY = 0.0;
p = inverse.Transform(p);
int w = tb.Source.PixelWidth;
int h = tb.Source.PixelHeight;
p = new Point((p.X + w) % w, (p.Y + h) % h);
}
TheLabel.Content = p.ToString();
}
}
For the most part this works well, but if you hover in the grey to the left of the rotated image (roughly where the X is in the screenshot below), you get a y-coordinate (0.5) that makes it look like you are in the image, when in reality you are outside, and the y-coordinate should be higher than the image height to reflect this.
This is important because I'm trying to allow the user to select an ROI, and I need to know when the selection is beyond the image bounds, though I still want to allow it.
You may perform the transformation on a "test point" inside the image bounds (e.g. the center) and the modulo operation on the transformed test point. Then use the offset between the transformed test point and the adjusted (by modulo) test point to adjust the actual point.
var p = e.GetPosition(TheImage);
p = new Point(
p.X * bs.PixelWidth / TheImage.ActualWidth,
p.Y * bs.PixelHeight / TheImage.ActualHeight);
if (TheImage.Source is TransformedBitmap tb)
{
var inverse = tb.Transform.Value;
inverse.Invert();
inverse.OffsetX = 0.0;
inverse.OffsetY = 0.0;
var w = tb.Source.PixelWidth;
var h = tb.Source.PixelHeight;
var v = new Vector(bs.PixelWidth / 2, bs.PixelHeight / 2); // test point
var v1 = inverse.Transform(v); // transformed test point
var v2 = new Vector((v1.X + w) % w, (v1.Y + h) % h); // adjusted
p = inverse.Transform(p) - v1 + v2; // add adjusting offset
}
TheLabel.Content = $"x: {p.X:F2}, y: {p.Y:F2}";
I have 'n' number of polygons as like below.
<Polygon Points="544,245,544,175,568,175,568,175" Stroke="Black" StrokeThickness="1" />
<Polygon Points="2,223,96,223,96,153,96,153" Stroke="Black" StrokeThickness="1" />
<Polygon Points="350,315,350,333,306,333,306,395,306,395" Stroke="Black" StrokeThickness="1" />
<Polygon Points="164,53,160,53,160,51,160,55,160,55" Stroke="Black" StrokeThickness="1" />
<Polygon Points="264,63,264,58,264,68,264,63,267,63,267,60,267,66,267,63,270,63,270,58,270,68,270,68" Stroke="Black" StrokeThickness="1" />
<Polygon Points="8,63,444,63,444,168,444,168" Stroke="Black" StrokeThickness="1" />
<Polygon Points="212,169,212,93,285,93,285,63,285,63" Stroke="Black" StrokeThickness="1" />
<Polygon Points="26,93,127,93,127,148,29,148,29,148" Stroke="Black" StrokeThickness="1" />
<Polygon Points="152,116,152,132,212,132,212,132" Stroke="Black" StrokeThickness="1" />
<Polygon Points="121,316,121,333,70,333,70,366,70,366" Stroke="Black" StrokeThickness="1" />
<Polygon Points="464,395,488,395,488,284,527,284,527,284" Stroke="Black" StrokeThickness="1" />
<Polygon Points="168,63,168,67,180,59,180,67,168,59,168,59" Stroke="Black" StrokeThickness="1" />
<Polygon Points="173,62,173,56,165,56,165,51,175,51,175,61,175,61" Stroke="Black" StrokeThickness="1" />
<Polygon Points="3,285,121,285,121,316,211,316,211,304,211,304" Stroke="Black" StrokeThickness="1" />
Please help me to identify what are the triangles in these polygons
I have tried to identify the vertices like below..
Polygon polygon = new Polygon();
polygon.Points = new System.Windows.Media.PointCollection()
{
new Point(446,134),
new Point(442,134),
new Point(444,140),
new Point(444,140),
};
List<double> verticesPoints = new List<double>();
for (int i = 0; i < polygon.Points.Count - 1; i++)
{
var point1 = polygon.Points[i];
var point2 = polygon.Points[i + 1];
//calculate delta x and delta y between the two points
var deltaX = Math.Pow((point2.X - point1.X), 2);
var deltaY = Math.Pow((point2.Y - point1.Y), 2);
//pythagras theorem for distance
var distance = Math.Sqrt(deltaY + deltaX);
//distance is zero..then same point
if (distance != 0)
{
verticesPoints.Add(distance);
}
}
///Here is the code to calculate angle and consider the triangle
///three vertices then it might be triangle.
if (verticesPoints.Count == 3)
{
///use The Law of Cosines
///cos(C) = a2 + b2 − c2 /2ab
///cos(A) = b2 + c2 − a2 /bc
///cos(B) = c2 + a2 − b2 /ca
var a = ((Math.Pow(verticesPoints[1], 2)) + (Math.Pow(verticesPoints[2], 2)) - (Math.Pow(verticesPoints[0], 2)))
/ (2 * verticesPoints[1] * verticesPoints[2]);
var b = ((Math.Pow(verticesPoints[0], 2)) + (Math.Pow(verticesPoints[2], 2)) - (Math.Pow(verticesPoints[1], 2)))
/ (2 * verticesPoints[0] * verticesPoints[2]);
var c = ((Math.Pow(verticesPoints[0], 2)) + (Math.Pow(verticesPoints[1], 2)) - (Math.Pow(verticesPoints[2], 2)))
/ (2 * verticesPoints[0] * verticesPoints[1]);
///Inverse of cos
var radians1 = Math.Acos(a);
///Convert radian to degree
double degrees1 = (radians1 * 180.0) / Math.PI;
///Inverse of cos
var radians2 = Math.Acos(b);
//Convert radian to degree
double degrees2 = (radians2 * 180.0) / Math.PI;
///Inverse of cos
var radians3 = Math.Acos(c);
///Convert radian to degree
double degrees3 = (radians3 * 180.0) / Math.PI;
var totalDegrees = degrees1 + degrees2 + degrees3;
if (totalDegrees == 180)
{
// Consider triangle
}
}
But above code is not working for <Polygon Points="446,134,442,134,444,140,444,140" Stroke="Black" StrokeThickness="1" /> it is giving only two vertices but it is a triangle and some scenario getting 3 vertices but totalDegrees is not as 180
This code here iterates through the points and calculates the gradient between each of them. If the gradient is the same for two sequential points they must be the same line, so the noOfPoints is not incremented otherwise it is incremented.
The first gradient is stored in firstGradient in order to check if the gradient connecting the last and first point is the same as the gradient between the first and second point.
Polygon polygon = new Polygon();
polygon.Points = new System.Windows.Media.PointCollection()
{
new Point(446,134),
new Point(442,134),
new Point(444,140),
new Point(444,140),
};
List<double> verticesPoints = new List<double>();
double? firstGradient = null;
double? gradient = null;
double? newGradient = null;
int noOfSides = 1;
for (int i = 0; i < polygon.Points.Count - 1; i++)
{
var point1 = polygon.Points[i];
var point2 = polygon.Points[i + 1];
if(point1 == point2) { continue;}
//calculate delta x and delta y between the two points
var deltaX = point2.X - point1.X;
var deltaY = point2.Y - point1.Y;
//calculate gradient
newGradient = (deltaY / deltaX);
if (i == 0)
{
firstGradient = newGradient;
}
if ((gradient != newGradient) && (i != polygon.Points.Count - 2))
{
noOfSides++;
}
else if (i == polygon.Points.Count - 2)
{
if ((gradient != newGradient) && (firstGradient != newGradient)) //This now checks the gradient between the last and first point.
{
point1 = polygon.Points[i+1];
point2 = polygon.Points[0];
if (point1 == point2) { continue; }
//calculate delta x and delta y between the two points
deltaX = point2.X - point1.X;
deltaY = point2.Y - point1.Y;
//calculate gradient
newGradient = (deltaY / deltaX);
if(newGradient != firstGradient)
{
noOfSides++;
}
}
gradient = newGradient;
}
I have solved the above problem using "AForge.NET"
Polygon polygon = new Polygon();
polygon.Points = new PointCollection()
{
new Point(446,134),
new Point(442,134),
new Point(444,140),
new Point(444,140),
};
SimpleShapeChecker shapeChecker = new SimpleShapeChecker();
List<IntPoint> edgePoints = new List<IntPoint>();
List<IntPoint> corners;
for (int i = 0; i <= polygon.Points.Count - 1; i++)
{
edgePoints.Add(new IntPoint((int)polygon.Points[i].X, (int)polygon.Points[i].Y));
}
shapeChecker.MinAcceptableDistortion = 0.2f;
shapeChecker.LengthError = 0;
shapeChecker.AngleError = 5;
shapeChecker.RelativeDistortionLimit = 0;
if (shapeChecker.IsTriangle(edgePoints, out corners))
{
//shape is triangle
}
Need to add below namespace
using AForge;
using AForge.Math.Geometry;
Reference :http://aforgenet.com/articles/shape_checker/
I need to create a grid using Canvas with horizontal and vertical lines. The problem is in very bad performance when I'm using dashed lines instead of solid. Is there any solution to solve this? I don't need the possibility to handle events of this dashed lines (maybe exists some 'lightweight' version of canvas object...).
If I add StrokeDashArray to the Line object the application is very slow...
private void DrawGrid()
{
var brush = new SolidColorBrush((Color) ColorConverter.ConvertFromString("#cccccc"));
for (int i = 100; i < _areaSize; i += 100)
{
var hLine = new Line
{
X1 = 0,
Y1 = i,
X2 = _areaSize,
Y2 = i,
Stroke = brush,
StrokeThickness = 1,
SnapsToDevicePixels = true,
};
var vLine = new Line
{
X1 = i,
Y1 = 0,
X2 = i,
Y2 = _areaSize,
Stroke = brush,
StrokeThickness = 1,
SnapsToDevicePixels = true
};
//hLine.SetValue(RenderOptions.EdgeModeProperty, EdgeMode.Aliased);
//vLine.SetValue(RenderOptions.EdgeModeProperty, EdgeMode.Aliased);
Container.Children.Add(hLine);
Container.Children.Add(vLine);
Panel.SetZIndex(hLine, -1000);
Panel.SetZIndex(vLine, -1000);
}
}
I need help to bound the path or canvas in a boundary. What I want is, when I click the mouse-left-button, holding it and move for panning. It should not move when mouse pointer reach some boundary as BORDER. I'll add code here please help me out
XAML code:
<Border x:Name="OutMoastBorder" Height="820" Width="820" ClipToBounds="True" BorderThickness="2" BorderBrush="Black" Block.IsHyphenationEnabled="True">
<Border x:Name="clipBorder" Height="810" Width="810" BorderThickness="2" BorderBrush="Black" ClipToBounds="True">
<Canvas x:Name="CanvasPanel" Height="800" Width="800" Background="Beige">
</Canvas>
</Border>
</Border>
<Grid>
<Button Content="Original Size" Height="23" Name="btn_Original" Width="75" Click="btn_Original_Click" Margin="4,4,921,973" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="4,59,0,0" Name="txtNoOfZones" VerticalAlignment="Top" Width="120" MaxLength="2" PreviewTextInput="txtNoOfZones_PreviewTextInput" />
<Label Content="Enter a number below for No. of Zones" Height="28" HorizontalAlignment="Left" Margin="4,33,0,0" Name="label1" VerticalAlignment="Top" Width="220" FontFamily="Vijaya" FontSize="15" FontWeight="Bold" FontStyle="Normal" />
<Button Content="Zones" Height="23" HorizontalAlignment="Left" Margin="130,58,0,0" Name="btnNoOfZones" VerticalAlignment="Top" Width="41" Click="btnNoOfZones_Click" />
</Grid>
</Grid>
Code behind for zooming and panning:
void Zoom_MouseWheel(object sender, MouseWheelEventArgs e)
{
Point p = e.MouseDevice.GetPosition(((Path)sender));
Matrix m = CanvasPanel.RenderTransform.Value;
if (e.Delta > 0)
m.ScaleAtPrepend(1.1, 1.1, p.X, p.Y);
else
m.ScaleAtPrepend(1 / 1.1, 1 / 1.1, p.X, p.Y);
CanvasPanel.RenderTransform = new MatrixTransform(m);
// CanvasPanel.RenderTransformOrigin = new Point(0.5, 0.5);
}
private Point origin;
private Point start;
void Pan_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (((Path)sender).IsMouseCaptured) return;
((Path)sender).CaptureMouse();
start = e.GetPosition(clipBorder);
origin.X = CanvasPanel.RenderTransform.Value.OffsetX;
origin.Y = CanvasPanel.RenderTransform.Value.OffsetY;
}
void Pan_MouseLeftBtnUp(object sender, MouseButtonEventArgs e)
{
((Path)sender).ReleaseMouseCapture();
}
void Pan_MouseMove(object sender, MouseEventArgs e)
{
if (!((Path)sender).IsMouseCaptured) return;
Point p = e.MouseDevice.GetPosition(clipBorder);
Matrix m = CanvasPanel.RenderTransform.Value;
m.OffsetX = origin.X + (p.X - start.X);
m.OffsetY = origin.Y + (p.Y - start.Y);
CanvasPanel.RenderTransform = new MatrixTransform(m);
}
private const int NoOfSectors = 180;
private const int NoOfZones = 5;
void OnLoaded(object sender, RoutedEventArgs args)
{
const double PIES = 2 * Math.PI / NoOfSectors;
Point center = new Point(CanvasPanel.ActualWidth / 2, CanvasPanel.ActualHeight / 2); // center (x,y) Co-ordinates to get center of canvas
double radius = 0.1 * Math.Min(CanvasPanel.ActualWidth, CanvasPanel.ActualHeight);
for (int s = 0; s <= NoOfSectors; s++)
{
for (int z = 1; z <= NoOfZones; z++)
{
Path path = new Path();
PathGeometry pathGeo = new PathGeometry();
PathFigure pathFig = new PathFigure();
double radians = 2 * Math.PI * s / NoOfSectors;
double outerRadius = radius * z;
double innerRadius = radius * ((z - 1));
Size outerArcSize = new Size(outerRadius, outerRadius);
Size innerArcSize = new Size(innerRadius, innerRadius);
Point p1_1st_LineSegment; //------------------------------> Points variable, to store each iterate point in these.
Point p2_1st_ArcSegment;
Point p3_2nd_LineSegment;
Point p4_2nd_ArcSegment;
p1_1st_LineSegment = new Point(center.X + innerRadius * Math.Cos(radians - PIES), center.Y - innerRadius * Math.Sin(radians - PIES)); // point for LINE from Center
p2_1st_ArcSegment = new Point(center.X + innerRadius * Math.Cos(radians), center.Y - innerRadius * Math.Sin(radians)); // point for ARC after the 1st LINE formation
p3_2nd_LineSegment = new Point(center.X + outerRadius * Math.Cos(radians), center.Y - outerRadius * Math.Sin(radians)); // Point for 2nd LINE after forming both LINE abd ARC
p4_2nd_ArcSegment = new Point(center.X + outerRadius * Math.Cos(radians - PIES), center.Y - outerRadius * Math.Sin(radians - PIES)); // Point for 2nd ARC which is Counter-CLockwise that closes a path
pathFig.StartPoint = center;
pathFig.Segments.Add(new LineSegment(p1_1st_LineSegment, true));
pathFig.Segments.Add(new ArcSegment(p2_1st_ArcSegment, innerArcSize, 1, false, SweepDirection.Clockwise, true));
pathFig.Segments.Add(new LineSegment(p3_2nd_LineSegment, true));
pathFig.Segments.Add(new ArcSegment(p4_2nd_ArcSegment, outerArcSize, 1, false, SweepDirection.Counterclockwise, true));
pathFig.IsClosed = false; //false because, path has to be close with ARC, not with LINE
pathFig.IsFilled = true;
pathGeo.Figures.Add(pathFig); // binding data to a Geometry
pathGeo.FillRule = FillRule.Nonzero;
path.Data = pathGeo; // binding whole geometry data to a path
path.Stroke = Brushes.Black;
path.Fill = Brushes.Silver;
path.StrokeThickness = 0.1;
// CanvasPanel.RenderTransformOrigin = new Point(0.5, 0.5); //--------------------> this makes "Canvas" to be Zoom from center
CanvasPanel.Children.Add(path); // binding to a CanvasPanel as a children
path.MouseLeftButtonDown += MouseLeftButtonClick; // calling Mouse-click-event
path.MouseWheel += Zoom_MouseWheel;
path.MouseLeftButtonDown += Pan_MouseLeftButtonDown;
path.MouseLeftButtonUp += Pan_MouseLeftBtnUp;
path.MouseMove += Pan_MouseMove;
}
}
Please help me out.
Regards,
Viswanath.
PLz replace the MouseMove event with this following code in code behind.
void Pan_MouseMove(object sender, MouseEventArgs e)
{
if (!((Path)sender).IsMouseCaptured) return;
Point p = e.MouseDevice.GetPosition(clipBorder);
if (p.X > 0 && p.Y > 0 && p.X < clipBorder.ActualWidth && p.Y < clipBorder.ActualHeight)
{
Matrix m = CanvasPanel.RenderTransform.Value;
m.OffsetX = origin.X + (p.X - start.X);
m.OffsetY = origin.Y + (p.Y - start.Y);
CanvasPanel.RenderTransform = new MatrixTransform(m);
}
}
I have tested, this will surely solve this.
regards,
Viswa
Currently, I have a random terrain generator, which I am sure works properly, however, when I attempt to build a set of VertexPositionColor, it does not render properly. This is what I currently have (overhead view):
My code:
List<VertexPositionColor> w = new List<VertexPositionColor>();
int width = 20;
int height = 20;
float terrainScale = 2.0f;
long seed = (DateTime.Now.Millisecond + DateTime.Now.Second * DateTime.Now.Hour);
ProceduralLayeredMapGenerator plmg = new ProceduralLayeredMapGenerator(seed);
Random rand = new Random((int)seed);
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
Vector3 position = new Vector3();
position.X = x;//(x - width / 2) * terrainScale;
position.Z = y;//(y - height / 2) * terrainScale;
float point = plmg.getPoint(x, y);
Color computedColor = new Color(rand.Next(255), rand.Next(255), rand.Next(255));
position.Y = (point * 2);
w.Add(new VertexPositionColor(position, computedColor));
}
}
colors = w.ToArray();
And then the drawing code:
foreach (EffectPass pass in effect.CurrentTechnique.Passes)
{
pass.Apply();
GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleStrip, colors, 0, colors.Length / 3, VertexPositionColor.VertexDeclaration);
}
How can I get it to look something more like this:
If you want to draw a TriangleStrip then you must add the vertices in the order in which they will be used to draw the triangles; right now you're adding vertices top-to-bottom, left-to-right. Also, to render a height map like that you'll need to use multiple TriangleStrips.