I draw in a canvas several shapes. I have two kinds of shapes : Ellipse, and Path.
Now when I make a click on my Canvas, I want to get the nearest Shape.
I could manage to do something for Ellipse, but for Path I don't manage to find how to get its coordinates.
Here is the code I use to generate a List, in case something is not optimum in that method :
Concretely, "percage" are drillings, if Type=12, it means I draw a slot(Path). else I draw a circle(Ellipse)
if (percage.Type == 12)
{
double r = percage.Diametre / 2;
LineSegment ligne1 = new LineSegment();
LineSegment ligne2 = new LineSegment();
Point ptCentre = new Point(dx + percage.Coor_X, this.MyScrollViewer.ActualHeight * echelle - dy - percage.Coor_Y);
double angle = percage.AnglePer;
double xLeft = ptCentre.X - r;
double xRight = ptCentre.X + r;
double yUp = ptCentre.Y - ((percage.Longueur / 2) - r);
double yDown = ptCentre.Y + ((percage.Longueur / 2) - r);
Point pt1 = new Point(xLeft, yUp);
Point pt2 = new Point(xRight, yUp);
Point pt3 = new Point(xRight, yDown);
Point pt4 = new Point(xLeft, yDown);
pt1 = Global.RotatePoint(pt1, ptCentre, angle - 90);
pt2 = Global.RotatePoint(pt2, ptCentre, angle - 90);
pt3 = Global.RotatePoint(pt3, ptCentre, angle - 90);
pt4 = Global.RotatePoint(pt4, ptCentre, angle - 90);
Path arc_path1 = new Path();
arc_path1.Stroke = Brushes.Red;
arc_path1.StrokeThickness = 2;
PathGeometry pathGeometry = new PathGeometry();
ArcSegment arc1 = new ArcSegment();
ArcSegment arc2 = new ArcSegment();
PathFigure pathfigure1 = new PathFigure();
PathFigure pathfigure2 = new PathFigure();
arc1.Point = new Point(pt2.X, pt2.Y);
arc1.Point = new Point(pt4.X, pt4.Y);
pathfigure1.StartPoint = new Point(pt1.X, pt1.Y);
pathfigure1.StartPoint = new Point(pt3.X, pt3.Y);
SweepDirection sd = SweepDirection.Counterclockwise;
if (yUp < yDown)
{
sd = SweepDirection.Clockwise;
}
arc1.Size = new Size(r, r);
arc1.SweepDirection = sd;
arc2.Size = new Size(r, r);
arc2.SweepDirection = sd;
arc1.Point = pt2;
arc2.Point = pt4;
ligne1.Point = new Point(pt3.X, pt3.Y);
ligne2.Point = new Point(pt1.X, pt1.Y);
pathfigure1.StartPoint = new Point(pt1.X, pt1.Y);
pathfigure1.Segments.Add(arc1);
pathfigure1.Segments.Add(ligne1);
pathfigure1.Segments.Add(arc2);
pathfigure1.Segments.Add(ligne2);
pathGeometry.Figures.Add(pathfigure1);
arc_path1.Data = pathGeometry;
arc_path1.Tag = percage;
percage.ListShapes.Add(arc_path1);
}
else
{
Ellipse ellipse = new Ellipse();
ellipse.Stroke = System.Windows.Media.Brushes.Red;
ellipse.StrokeThickness = 1;
ellipse.Fill = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Red);
ellipse.Width = percage.Diametre;
ellipse.Height = percage.Diametre;
percage.Coor_X_Graph = X1 + dx - (percage.Diametre / 2);
percage.Coor_Y_Graph = this.MyScrollViewer.ActualHeight * echelle - (Y1 + dy) - (percage.Diametre / 2);
ellipse.Margin = new System.Windows.Thickness(percage.Coor_X_Graph, percage.Coor_Y_Graph, 0, 0);
ellipse.Tag = percage;
percage.ListShapes.Add(ellipse);
}
Then, to get the nearest shape, I began that code :
For ellipse I can retrieve its coordinates, but for Path, couldn't find the List of Segments inside.
StartPoint = e.GetPosition(monDessin);
double distance=-1;
Shape selectedShape = null;
for (int i = monDessin.Children.Count - 1; i > -1; i--)
{
if (monDessin.Children[i] is Ellipse)
{
Ellipse ell = (Ellipse)monDessin.Children[i];
double x = ell.Margin.Left + Width / 2;
double y = ell.Margin.Top - ell.Height / 2;
double dist = Math.Sqrt((StartPoint.X - x) * (StartPoint.X - x) + (StartPoint.Y -y) * (StartPoint.Y - y));
if(distance==-1 || dist<distance)
{
distance = dist;
}
}
else if(monDessin.Children[i] is Path)
{
Path path=(Path)monDessin.Children[i];
Geometry geometry = path.Data;
foreach(PathFigure pf in ?????)
}
}
Finally, I could do it myself looking a bit more on internet (not sure it is the best way as I began on that theme,so any other suggestion is welcome)
I found some "solution" here
But the code didn't work, got an error on the following line (without understand why, neither what it does)
string value = seralizer.ConvertToString(geomerty, null);
Finally I adapted it looking on msdn website, I found geometry.GetOutlinedPathGeometry() and pathGeometry.Figures that allowed to get list of figures. I just don't understand why all my ArcSegment became BezierSegment.
Anyway, it works fine, so I put here the code :
StartPoint = e.GetPosition(monDessin);
double distance=-1;
double dist;
Shape selectedShape = null;
for (int i = monDessin.Children.Count - 1; i > -1; i--)
{
string type = monDessin.Children[i].GetType().ToString();
if (monDessin.Children[i] is Ellipse)
{
Ellipse ell = (Ellipse)monDessin.Children[i];
double x = ell.Margin.Left + ell.Width / 2;
double y = ell.Margin.Top - ell.Height / 2;
dist = Math.Sqrt((StartPoint.X - x) * (StartPoint.X - x) + (StartPoint.Y -y) * (StartPoint.Y - y));
if(distance==-1 || dist<distance)
{
distance = dist;
}
}
else if(monDessin.Children[i] is Path)
{
Path path=(Path)monDessin.Children[i];
string titi = path.Tag.GetType().ToString();
Geometry geometry = path.Data;
PathGeometry pathGeometry = geometry.GetOutlinedPathGeometry();
PathFigureCollection figures = pathGeometry.Figures;
if (figures != null)
{
foreach (PathFigure figure in figures)
{
foreach (PathSegment segment in figure.Segments)
{
//first syntax : if(segment is LineSegment)
if(segment is LineSegment)
{
LineSegment lineSegment = (LineSegment)segment;
double x = lineSegment.Point.X;
ouble y = lineSegment.Point.Y;
}
//2nd syntax :
//ArcSegment arcSegment = segment as ArcSegment;
//Then check if not null
ArcSegment arcSegment = segment as ArcSegment;
if (arcSegment != null)
{
double x = arcSegment.Point.X;
double y = arcSegment.Point.Y;
dist = Math.Sqrt((StartPoint.X - x) * (StartPoint.X - x) + (StartPoint.Y - y) * (StartPoint.Y - y));
if (distance == -1 || dist < distance)
{
distance = dist;
}
}
BezierSegment bezierSegment = segment as BezierSegment;
if (bezierSegment != null)
{
double x = bezierSegment.Point3.X;
double y = bezierSegment.Point3.Y;
dist = Math.Sqrt((StartPoint.X - x) * (StartPoint.X - x) + (StartPoint.Y - y) * (StartPoint.Y - y));
if (distance == -1 || dist < distance)
{
distance = dist;
}
}
}
}
}
}
}
Nota : I found two different syntaxes to check the type of segment(i.e LineSegment), I don't know which is the best approach, the first syntax seems better, but the 2nd one has the advantage to check if it is null(even if in theory it may never happen???)
Related
I am making an automated floor plan generation desktop app. In this, at first I draw the polygon on points, using this method
public void DrawPolygonPointF(PaintEventArgs e) {
// Create pen.
Pen blackPen = new Pen(Color.Black, 3);
// Create points that define polygon.
PointF point1 = new PointF(50.0F, 50.0F);
PointF point2 = new PointF(100.0F, 25.0F);
PointF point3 = new PointF(200.0F, 5.0F);
PointF point4 = new PointF(250.0F, 50.0F);
PointF point5 = new PointF(300.0F, 100.0F);
PointF point6 = new PointF(350.0F, 200.0F);
PointF point7 = new PointF(250.0F, 250.0F);
PointF[] curvePoints =
{
point1,
point2,
point3,
point4,
point5,
point6,
point7
};
// Draw polygon curve to screen.
e.Graphics.DrawPolygon(blackPen, curvePoints);
}
NOTE: These points are not actual points, they are for only demo purpose. I am reading the points from a text file.
Now I need to generate a special type of grid.
In generating the grid the first step is detect corners and extend the corner lines.
How do I detect the corners of a polygon so can i move to next step of generating grid?
Corners are marked. I need to extend corner marked with black on horizontally left side and other one is to extend on right side till its touch the line.
A screenshot is attached.
Thanks in advance
In my understanding you are trying to extend edges, not corners.
the procedure could be:
Enumerate edges (each 2 adjacent points define an edge)
For each edge find if it vertical or horizontal (abs(x1-x2) > abs(y1-y2))
Find if edge can be extended, the horizontal (left/right) and the vertical (up/bottom)
bool CheckHorizontalExtensibilityToRight(Point[] curvePoints, Point corner)
{
return curvePoints.Any(cp=>cp.Y < corner.Y && cp.X < corner.X);
}
Try this example and see if you can adapt it to solve your problem...
using System.Drawing;
using System.Windows.Forms;
namespace WindowsFormsApplication
{
public partial class Form1 : Form
{
private struct LineSegment
{
private PointF _a, _b;
public PointF A { get { return _a; } }
public PointF B { get { return _b; } }
public LineSegment(PointF a, PointF b)
{
_a = a; _b = b;
}
public float GetLengthSquared()
{
var dx = _a.X - _b.X;
var dy = _a.Y - _b.Y;
return dx * dx + dy * dy;
}
public bool RectContains(PointF a)
{
var x = a.X;
var y = a.Y;
var x1 = _a.X;
var y1 = _a.Y;
var x2 = _b.X;
var y2 = _b.Y;
return (x1 < x2 ? x1 <= x && x2 >= x : x2 <= x && x1 >= x) && (y1 < y2 ? y1 <= y && y2 >= y : y2 <= y && y1 >= y);
}
public bool ExtendToIntersectWith(LineSegment b)
{
var x1 = _a.X;
var y1 = _a.Y;
var x2 = _b.X;
var y2 = _b.Y;
var x3 = b._a.X;
var y3 = b._a.Y;
var x4 = b._b.X;
var y4 = b._b.Y;
var a1 = y2 - y1;
var b1 = x1 - x2;
var c1 = x1 * y2 - x2 * y1;
var a2 = y4 - y3;
var b2 = x3 - x4;
var c2 = x3 * y4 - x4 * y3;
var d = a1 * b2 - b1 * a2;
if (d == 0)
return false;
var x = (c1 * b2 - b1 * c2) / d;
var y = (a1 * c2 - c1 * a2) / d;
var p = new PointF(x, y);
if (b.RectContains(p) && !RectContains(p))
{
if (new LineSegment(_a, p).GetLengthSquared() < new LineSegment(_b, p).GetLengthSquared())
_a = p;
else
_b = p;
return true;
}
return false;
}
}
public Form1()
{
InitializeComponent();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
PointF[] curvePoints =
{
/*
new PointF(50.0F, 50.0F),
new PointF(100.0F, 25.0F),
new PointF(200.0F, 5.0F),
new PointF(250.0F, 50.0F),
new PointF(300.0F, 100.0F),
new PointF(350.0F, 200.0F),
new PointF(250.0F, 250.0F)
*/
new PointF(30F, 10F),
new PointF(60F, 10F),
new PointF(60F, 20F),
new PointF(90F, 20F),
new PointF(90F, 60F),
new PointF(10F, 60F),
new PointF(10F, 40F),
new PointF(30F, 40F),
};
int n = curvePoints.Length;
LineSegment[] lineSegments = new LineSegment[n];
int i = 0;
for (; i < n - 1; ++i)
lineSegments[i] = new LineSegment(curvePoints[i], curvePoints[i + 1]);
lineSegments[i] = new LineSegment(curvePoints[i], curvePoints[0]);
for (i = 0; i < n; ++i)
for (int j = 0; j < n; ++j)
lineSegments[i].ExtendToIntersectWith(lineSegments[j]);
for (i = 0; i < n; ++i)
{
var lineSegment = lineSegments[i];
e.Graphics.DrawLine(Pens.Black, lineSegment.A, lineSegment.B);
}
//e.Graphics.DrawPolygon(Pens.Black, curvePoints);
}
}
}
I am developing a system just like Camera mouse or other face control mouse, I have implemented all the functionality, the mouse pointer is also moving well, but I want to create the movement smooth just like the mouse control the pointer. the code I am using is:
if (startButton == true)
{
try
{
cap = new Capture();
pictureBox1.Image = cap.QueryFrame().ToImage<Bgr, Byte>().Bitmap;
}
catch (Exception exp)
{
MessageBox.Show("Error:" + exp);
}
_cascadeClassifier = new CascadeClassifier(Application.StartupPath + "/haarcascade_frontalface_default.xml");
eye_cascadeClassifier = new CascadeClassifier(Application.StartupPath + "/haarcascade_eye.xml");
timer1.Start();
}
private void timer1_Tick(object sender, EventArgs e)
{
using (var imageFrame = cap.QueryFrame().ToImage<Bgr, Byte>().Flip(FlipType.Horizontal))
{
if (imageFrame != null)
{
var grayframe = imageFrame.Convert<Gray, byte>();
var faces = _cascadeClassifier.DetectMultiScale(grayframe, 1.1, 10, Size.Empty); //the actual face detection happens here
foreach (var face in faces)
{
if(Configure.FaceBoxCheck==true)
imageFrame.Draw(face, new Bgr(Color.LightGreen), 2); //the detected face(s) is highlighted here using a box that is drawn around it/them
Int32 yCoordStartSearchEyes = face.Top + (face.Height * 3 / 11);
Point startingPointSearchEyes = new Point(face.X, yCoordStartSearchEyes);
Size searchEyesAreaSize = new Size(face.Width, (face.Height * 3 / 11));
Rectangle possibleROI_eyes = new Rectangle(startingPointSearchEyes, searchEyesAreaSize);
int widthNav = (imageFrame.Width / 11 * 3);
int heightNav = (imageFrame.Height / 11 * 3);
Rectangle nav = new Rectangle(new Point(imageFrame.Width / 2 - widthNav / 2, imageFrame.Height / 2 - heightNav / 2), new Size(widthNav, heightNav));
imageFrame.Draw(nav, new Bgr(Color.Lavender), 3);
Point cursor = new Point(face.X + searchEyesAreaSize.Width / 2, yCoordStartSearchEyes + searchEyesAreaSize.Height / 2);
grayframe.ROI = possibleROI_eyes;
var eyes = eye_cascadeClassifier.DetectMultiScale(grayframe, 2.15, 3, Size.Empty);
foreach (var eye in eyes)
{
//imageFrame.Draw(eye, new Bgr(Color.Red), 2);
if(Configure.EyeBoxCheck==true)
imageFrame.Draw(possibleROI_eyes, new Bgr(Color.DarkGreen), 2);
if (nav.Left < cursor.X && cursor.X < (nav.Left + nav.Width) && nav.Top < cursor.Y && cursor.Y < nav.Top + nav.Height)
{
LineSegment2D CursorDraw = new LineSegment2D(cursor, new Point(cursor.X, cursor.Y + 1));
imageFrame.Draw(CursorDraw, new Bgr(Color.White), 3);
//we compute new cursor coordinate using a simple scale based on frame width and height
int xCoord = (imageFrame.Width * (cursor.X - nav.Left)) / nav.Width;
int yCoord = (imageFrame.Height * (cursor.Y - nav.Top)) / nav.Height;
//We set our new cursor position
Cursor.Position = new Point(xCoord * 2, yCoord *2);
}
}
}
Ok, I'm sure there are a lot of other better ways, but this is a quick&dirty way of moving cursor position in a "Smooth" way from point a to point b. Of course this implementation can and should be optimized using a different thread instead of using Application.DoEvents() to avoid blocking the UI thread, but i hope this gets you on the track. First, how you should use it. Instead of:
Cursor.Position = new Point(xCoord * 2, yCoord *2);
You should do this:
MoveCursorSmooth(Cursor.Position, new Point(xCoord * 2, yCoord *2));
Now, the implementation of MoveCursorSmooth:
private void MoveCursorSmooth(Point a, Point b)
{
var step = 5;
var left = Math.Min(a.X, b.X);
var right = Math.Max(a.X, b.X);
int width = right - left;
var top = a.Y;
var bottom = b.Y;
int height = bottom - top;
if (width > height)
{
double slope = (double)height / (double)width;
if (a.X <= b.X)
for (int x = 1; x < width; ++x)
{
Cursor.Position = new Point((left + x), (a.Y + ((int)(slope * x + 0.5))));
System.Threading.Thread.Sleep(step);
Application.DoEvents();
}
else
for (int x = 1; x < width; ++x) // xOffset
{
Cursor.Position = new Point((right - x), (a.Y + ((int)(slope * x + 0.5))));
System.Threading.Thread.Sleep(step);
Application.DoEvents();
}
}
else
{
double slope = (double)width / (double)height;
if (a.X <= b.X)
{
for (int y = 1; y < height; ++y)
{
Cursor.Position = new Point((a.X + ((int)(slope * y + 0.5))), (top + y));
System.Threading.Thread.Sleep(step);
Application.DoEvents();
}
}
else
{
for (int y = 1; y < height; ++y)
{
Cursor.Position = new Point((b.X + ((int)(slope * y + 0.5))), (bottom - y));
System.Threading.Thread.Sleep(step);
Application.DoEvents();
}
}
}
}
This method is based on this answer
I'm able to point zoom on the Mandelbrot set, as long as the mouse doesn't move after zooming has begun. I've tried calculating a normalized delta (new coordinate - old coordinate)*(oldzoom), but what happens is the image appears to jump around to a new location. I've seen this issue before. I'm struggling more here because I have to somehow convert this mouse position delta back to the -2,2 coordinate space of the Mandelbrot set.
Here's my code. What's important is the GetZoomPoint method, and then the lines of code that define x0 and y0. Also, I use the Range class to scale values from one range to another. I WAS using deltaTrans (thats the thing I was talking about earlier where I normalize the mouse delta with the old scale).
using OpenTK.Graphics.OpenGL;
using SpriteSheetMaker;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Fractal.Fractal
{
public class Mandelbrot : BaseTexture
{
private static Transform GlobalTransform = SpriteSheetMaker.Global.Transform;
private static Vector3 GlobalScale = GlobalTransform.Scale;
private static Vector3 GlobalTrans = GlobalTransform.Translation;
private static Vector3 LastWindowPoint = null;
private static Vector3 ZoomFactor = Vector3.ONE * 1.2f;
private static Vector3 Displacement = Vector3.ZERO;
private static int WindowSize = 100;
public static Vector3 GetZoomPoint()
{
var zP = OpenGLHelpers.LastZoomPoint.Clone();
if (LastWindowPoint == null)
{
LastWindowPoint = zP.Clone();
}
var delta = zP - LastWindowPoint;
var oldZoom = GlobalScale / ZoomFactor;
var deltaTrans = delta.XY * oldZoom.XY;
var factor = ZoomFactor.Clone();
Range xR = new Range(0, WindowSize);
Range yR = new Range(0, WindowSize);
Range complexRange = new Range(-2, 2);
// Calculate displacement of zooming position.
var dx = (zP.X - Displacement.X) * (factor.X - 1f);
var dy = (zP.Y - Displacement.Y) * (factor.Y - 1f);
// Compensate for displacement.
Displacement.X -= dx;
Displacement.Y -= dy;
zP -= Displacement;
var x = complexRange.ScaleValue(zP.X, xR);
var y = complexRange.ScaleValue(zP.Y, yR);
var rtn = new Vector3(x, y);
LastWindowPoint = zP.Clone();
return rtn;
}
public static Mandelbrot Generate()
{
var size = new Size(WindowSize, WindowSize);
var radius = new Size(size.Width / 2, size.Height / 2);
Bitmap bmp = new Bitmap(size.Width, size.Height);
LockBitmap.LockBitmapUnsafe lbm = new LockBitmap.LockBitmapUnsafe(bmp);
lbm.LockBits();
var pt = Mandelbrot.GetZoomPoint();
Parallel.For(0, size.Width, i =>
{
// float x0 = complexRangeX.ScaleValue(i, xRange);
float x0 = ((i - radius.Width) / GlobalScale.X) + pt.X;
Parallel.For(0, size.Height, j =>
{
// float y0 = complexRangeY.ScaleValue(j, yRange);
float y0 = ((j - radius.Height) / GlobalScale.Y) + pt.Y;
float value = 0f;
float x = 0.0f;
float y = 0.0f;
int iteration = 0;
int max_iteration = 100;
while (x * x + y * y <= 4.0 && iteration < max_iteration)
{
float xtemp = x * x - y * y + x0;
y = 2.0f * x * y + y0;
x = xtemp;
iteration += 1;
if (iteration == max_iteration)
{
value = 255;
break;
}
else
{
value = iteration * 50f % 255f;
}
}
int v = (int)value;
lbm.SetPixel(i, j, new ColorLibrary.HSL(v / 255f, 1.0, 0.5).ToDotNetColor());
});
});
lbm.UnlockBits();
var tex = new BaseTextureImage(bmp);
var rtn = new Mandelbrot(tex);
return rtn;
}
public override void Draw()
{
base._draw();
}
private Mandelbrot(BaseTextureImage graphic)
{
var topLeft = new Vector3(0, 1);
var bottomLeft = new Vector3(0, 0);
var bottomRight = new Vector3(1, 0);
var topRight = new Vector3(1, 1);
this.Vertices = new List<Vector3>()
{
topLeft,bottomLeft,bottomRight,topRight
};
this.Size.X = WindowSize;
this.Size.Y = WindowSize;
this.Texture2D = graphic;
}
}
}
I refactored my code, and also figured out a solution to this problem. 2 big wins in one. Ok, so I found a solution on CodeProject written in C# which I was readily able to adapt to my project. I'm not sure why I didn't realize this when I posted the question, but what I needed to solve this issue was to create a 'window' of zoom and not think in terms of a 'point zoom'. Yes, even if I am trying to zoom directly into a point, that point is just the center of some sort of a window.
Here is the method I have, which expects start and end mousedown coordinates (screen space), and converts the mandelbrot set window size accordingly.
public void ApplyZoom(double x0, double y0, double x1, double y1)
{
if (x1 == x0 && y0 == y1)
{
//This was just a click, no movement occurred
return;
}
/*
* XMin, YMin and XMax, YMax are the current extent of the set
* mx0,my0 and mx1,my1 are the part we selected
* do the math to draw the selected rectangle
* */
double scaleX, scaleY;
scaleX = (XMax - XMin) / (float)BitmapSize;
scaleY = (YMax - YMin) / (float)BitmapSize;
XMax = (float)x1 * scaleX + XMin;
YMax = (float)y1 * scaleY + YMin;
XMin = (float)x0 * scaleX + XMin;
YMin = (float)y0 * scaleY + YMin;
this.Refresh(); // force mandelbrot to redraw
}
Basically, whats happening is we calculate the ratio between the mandelbrot window size versus the screen size we are drawing to. Then, using that scale, we basically convert our mousedown coordinates to mandelbrot set coordinates (x1*scaleX, etc) and manipulate the current Min and Max coordinates with them, using the Min values as the pivot point.
Here's the link to the CodeProject I used as a reference: CodeProject link
* Update *
Found a solution using Clipper library. Solution added as answer. New / better / easier ideas are still welcome though!
Given a path like this:
I want to create a path surrounding this path with a given distance, e.g. 1 cm. The following sketch demonstrates that - the red path surrounds the black path with a distance of 1 cm.
How can this be done in a generic way using PDFSharp? (Meaning I want to finally draw it with PDFSharp, I don't care where the calculations are done)
Here is the code for the black path:
// helper for easily getting an XPoint in centimeters
private XPoint cmPoint(double x, double y)
{
return new XPoint(
XUnit.FromCentimeter(x),
XUnit.FromCentimeter(y)
);
}
// the path to be drawn
private XGraphicsPath getMyPath()
{
XGraphicsPath path = new XGraphicsPath();
XPoint[] points = new XPoint[3];
points[0] = cmPoint(0, 0);
points[1] = cmPoint(5, 2);
points[2] = cmPoint(10,0);
path.AddCurve(points);
path.AddLine(cmPoint(10, 0), cmPoint(10, 10));
path.AddLine(cmPoint(10, 10), cmPoint(0, 10));
path.CloseFigure();
return path;
}
// generate the PDF file
private void button3_Click(object sender, RoutedEventArgs e)
{
// Create a temporary file
string filename = String.Format("{0}_tempfile.pdf", Guid.NewGuid().ToString("D").ToUpper());
XPen penBlack = new XPen(XColors.Black, 1);
XPen penRed = new XPen(XColors.Red, 1);
PdfDocument pdfDocument = new PdfDocument();
PdfPage page = pdfDocument.AddPage();
page.Size = PdfSharp.PageSize.A1;
XGraphics gfx = XGraphics.FromPdfPage(page);
//give us some space to the left and top
gfx.TranslateTransform(XUnit.FromCentimeter(3), XUnit.FromCentimeter(3));
// draw the desired path
gfx.DrawPath(penBlack, getMyPath());
// Save the pdfDocument...
pdfDocument.Save(filename);
// ...and start a viewer
Process.Start(filename);
}
Thanks for any help on this topic!
You can use Widen() function, which replaces the path with curves that enclose the area that is filled when the path is drawn by a specified pen, adding an additional outline to the path.
This function receives as parameter a XPen, so you can create this XPen using the desired offset as width and an outer path will be added at a constant distance (pen's width).
XGraphicsPath class is in fact a wrapper of System.Drawing.Drawing2D.GraphicsPath, so you can use Widen() function in XGraphicsPath, get the internal object and iterate on it using GraphicsPathIterator class to get the path added.
This method will do the job:
public XGraphicsPath GetSurroundPath(XGraphicsPath path, double width)
{
XGraphicsPath container = new XGraphicsPath();
container.StartFigure();
container.AddPath(path, false);
container.CloseFigure();
var penOffset = new XPen(XColors.Black, width);
container.StartFigure();
container.Widen(penOffset);
container.CloseFigure();
var iterator = new GraphicsPathIterator(container.Internals.GdiPath);
bool isClosed;
var outline = new XGraphicsPath();
iterator.NextSubpath(outline.Internals.GdiPath, out isClosed);
return outline;
}
You can handle level of flatness in curves using the overload Widen(XPen pen, XMatrix matrix, double flatness). Doing this call container.Widen(penOffset, XMatrix.Identity, 0.05); results in more rounded edges.
Then draw an outer path using this function:
string filename = String.Format("{0}_tempfile.pdf", Guid.NewGuid().ToString("D").ToUpper());
XPen penBlack = new XPen(XColors.Black, 1);
XPen penRed = new XPen(XColors.Red, 1);
PdfDocument pdfDocument = new PdfDocument();
PdfPage page = pdfDocument.AddPage();
page.Size = PdfSharp.PageSize.A1;
XGraphics gfx = XGraphics.FromPdfPage(page);
//give us some space to the left and top
gfx.TranslateTransform(XUnit.FromCentimeter(3), XUnit.FromCentimeter(3));
var path = getMyPath();
// draw the desired path
gfx.DrawPath(penBlack, path);
gfx.DrawPath(penRed, GetSurroundPath(path, XUnit.FromCentimeter(1).Point));
// Save the pdfDocument...
pdfDocument.Save(filename);
// ...and start a viewer
Process.Start(filename);
This is what you get:
Another way may be using reflection to retrieve internal Pen in XPen and setup CompoundArray property. This allows you draws parallel lines and spaces. Using this property you can do something like this:
But the problem is that you can only use one color, anyway this is just an idea, I have not tried in PDFsharp
Also, you should search for offset polyline curves or offsetting polygon algorithms.
This can be done using Clipper
double scale = 1024.0;
List<IntPoint> points = new List<IntPoint>();
points.Add(new IntPoint(0*scale, 0*scale));
points.Add(new IntPoint(5*scale, 2*scale));
points.Add(new IntPoint(10*scale, 0*scale));
points.Add(new IntPoint(10*scale, 10*scale));
points.Add(new IntPoint(0*scale, 10*scale));
points.Reverse();
List<List<IntPoint>> solution = new List<List<IntPoint>>();
ClipperOffset co = new ClipperOffset();
co.AddPath(points, JoinType.jtMiter, EndType.etClosedPolygon);
co.Execute(ref solution, 1 * scale);
foreach (IntPoint point in solution[0])
{
Console.WriteLine("OUTPUT: " + point.X + "/" + point.Y + " -> " + point.X/scale + "/" + point.Y/scale);
}
And the output:
OUTPUT: 11264/11264 -> 11/11
OUTPUT: -1024/11264 -> -1/11
OUTPUT: -1024/-1512 -> -1/-1,4765625
OUTPUT: 5120/945 -> 5/0,9228515625
OUTPUT: 11264/-1512 -> 11/-1,4765625
Drawn original and offset path:
This is still not perfect for various mathematical reasons, but already quite good.
This is an updated Answer requested
The XGraphicPath is sealed class which was implemented with bad practices IMO, so the only way is to use a wrapper around it. I tried to make the code as self documented as possible
public class OGraphicPath
{
private readonly ICollection<XPoint[]> _curves;
private readonly ICollection<Tuple<XPoint, XPoint>> _lines;
public OGraphicPath()
{
_lines = new List<Tuple<XPoint, XPoint>>();
_curves = new List<XPoint[]>();
}
public XGraphicsPath XGraphicsPath
{
get
{
var path = new XGraphicsPath();
foreach (var curve in _curves)
{
path.AddCurve(curve);
}
foreach (var line in _lines)
{
path.AddLine(line.Item1, line.Item2);
}
path.CloseFigure();
return path;
}
}
public void AddCurve(XPoint[] points)
{
_curves.Add(points);
}
public void AddLine(XPoint point1, XPoint point2)
{
_lines.Add(new Tuple<XPoint, XPoint>(point1, point2));
}
// Finds Highest and lowest X and Y to find the Center O(x,y)
private XPoint FindO()
{
var xs = new List<double>();
var ys = new List<double>();
foreach (var point in _curves.SelectMany(points => points))
{
xs.Add(point.X);
ys.Add(point.Y);
}
foreach (var line in _lines)
{
xs.Add(line.Item1.X);
xs.Add(line.Item2.X);
ys.Add(line.Item1.Y);
ys.Add(line.Item2.Y);
}
var OX = xs.Min() + xs.Max()/2;
var OY = ys.Min() + ys.Max()/2;
return new XPoint(OX, OY);
}
// If a point is above O, it's surrounded point is even higher, if it's below O, it's surrunded point is below O too...
private double FindPlace(double p, double o, double distance)
{
var dp = p - o;
if (dp < 0)
{
return p - distance;
}
if (dp > 0)
{
return p + distance;
}
return p;
}
public XGraphicsPath Surrond(double distance)
{
var path = new XGraphicsPath();
var O = FindO();
foreach (var curve in _curves)
{
var points = new XPoint[curve.Length];
for (var i = 0; i < curve.Length; i++)
{
var point = curve[i];
var x = FindPlace(point.X, O.X, distance);
var y = FindPlace(point.Y, O.Y, distance);
points[i] = new XPoint(x, y);
}
path.AddCurve(points);
}
foreach (var line in _lines)
{
var ax = FindPlace(line.Item1.X, O.X, distance);
var ay = FindPlace(line.Item1.Y, O.Y, distance);
var a = new XPoint(ax, ay);
var bx = FindPlace(line.Item2.X, O.X, distance);
var by = FindPlace(line.Item2.Y, O.Y, distance);
var b = new XPoint(bx, by);
path.AddLine(a, b);
}
path.CloseFigure();
return path;
}
}
And is Consumed Like this
// draw the desired path
var path = getMyPath();
gfx.DrawPath(penBlack, path.XGraphicsPath);
gfx.DrawPath(penRed, path.Surrond(XUnit.FromCentimeter(1)));
What if we made a "DrawOutline" extension to xGraphics?
public static class XGraphicsExtentions
{
public static void DrawOutline(this XGraphics gfx, XPen pen, XGraphicsPath path, int offset)
{
// finding the size of the original path so that we know how much to scale it in x and y
var points = path.Internals.GdiPath.PathPoints;
float minX, minY;
float maxX, maxY;
GetMinMaxValues(points, out minX, out minY, out maxX, out maxY);
var deltaY = XUnit.FromPoint(maxY - minY);
var deltaX = XUnit.FromPoint(maxX - minX);
var offsetInPoints = XUnit.FromCentimeter(offset);
var scaleX = XUnit.FromPoint((deltaX + offsetInPoints)/deltaX);
var scaleY = XUnit.FromPoint((deltaY + offsetInPoints)/deltaY);
var transform = -offsetInPoints/2.0;
gfx.TranslateTransform(transform, transform);
gfx.ScaleTransform(scaleX, scaleY);
gfx.DrawPath(pen, path);
// revert changes to graphics object before exiting
gfx.ScaleTransform(1/scaleX,1/scaleY);
gfx.TranslateTransform(-transform, -transform);
}
private static void GetMinMaxValues(PointF[] points, out float minX, out float minY, out float maxX, out float maxY)
{
minX = float.MaxValue;
maxX = float.MinValue;
minY = float.MaxValue;
maxY = float.MinValue;
foreach (var point in points)
{
if (point.X < minX)
minX = point.X;
if (point.X > maxX)
maxX = point.X;
if (point.Y < minY)
minY = point.Y;
if (point.Y > maxY)
maxY = point.Y;
}
}
}
Usage:
// draw the desired path
gfx.DrawPath(penBlack, getMyPath());
gfx.DrawOutline(penRed, getMyPath(), 2);
Result:
Clipper is a great choice, but depending on your needs it will not result in a perfect offset.
offset from edge is not equal to offset from corner
A better solution, which will require you to remove any beizer curves and only use line primitives, is using CGAL library for contour offsets: http://doc.cgal.org/latest/Straight_skeleton_2/index.html
Another way of doing it, which is actually pretty cool (albeit taking a lot of memory), is to convert your path to a bitmap and then apply a dilate operation, https://en.wikipedia.org/wiki/Dilation_(morphology). This will give you a correct transformation, but in the bitmap resolution.
You can the convert the bitmap to vector graphics, using a tool like https://en.wikipedia.org/wiki/Potrace
A good image toolbox is OpenCV, and http://www.emgu.com/wiki/index.php/Main_Page for .NET/C#. It includes dilation.
This will give you a somewhat limited resolution approach, but the end result will be precise to the bitmap resolution (and actually a lot higher since you are using a contour offset that, in fact, is limiting the offsetted contour details).
try this:
public Lis<Point> Draw(Point[] points /*Current polygon*/, int distance /*distance to new polygon*/) {
List<Point> lResult = new List<Point>();
foreach(Point lPoint in points) {
Point lNewPoint = new Point(lPoint.X - distance, lPoint.Y);
if(!CheckCurrentPoint(lNewPoint, points)) {
lResult.Add(lNewPoint)
continue;
}
lNewPoint = new Point(lPoint.X + distance, lPoint.Y);
if(!CheckCurrentPoint(lNewPoint, points)) {
lResult.Add(lNewPoint)
continue;
}
lNewPoint = new Point(lPoint.X, lPoint.Y - distance);
if(!CheckCurrentPoint(lNewPoint, points)) {
lResult.Add(lNewPoint)
continue;
}
lNewPoint = new Point(lPoint.X, lPoint.Y + distance);
if(!CheckCurrentPoint(lNewPoint, points)) {
lResult.Add(lNewPoint)
continue;
}
}
return lResult; // Points of new polygon
}
private static int Crs(Point a1, Point a2, Point p, ref bool ans) {
const double e = 0.00000000000001;
int lCrsResult = 0;
if (Math.Abs(a1.Y - a2.Y) < e)
if ((Math.Abs(p.Y - a1.Y) < e) && ((p.X - a1.X) * (p.X - a2.X) < 0.0))
ans = false;
if ((a1.Y - p.Y) * (a2.Y - p.Y) > 0.0)
return lCrsResult;
double lX = a2.X - (a2.Y - p.Y) / (a2.Y - a1.Y) * (a2.X - a1.X);
if (Math.Abs(lX - p.X) < e)
ans = false;
else if (lX < p.X) {
lCrsResult = 1;
if ((Math.Abs(a1.Y - p.Y) < e) && (a1.Y < a2.Y))
lCrsResult = 0;
else if ((Math.Abs(a2.Y - p.Y) < e) && (a2.Y < a1.Y))
lCrsResult = 0;
}
return lCrsResult;
}
private static bool CheckCurrentPoint(Point p /*Point of new posible polygon*/, Points[] points /*points of current polygon*/) {
if (points.Count == 0)
return false;
int lC = 0;
bool lAns = true;
for (int lIndex = 1; lIndex < points.Count; lIndex++) {
lC += Crs(points[lIndex - 1], points[lIndex], p, ref lAns);
if (!lAns)
return false;
}
lC += Crs(points[points.Count - 1], points[0], p, ref lAns);
if (!lAns)
return false;
return (lC & 1) > 0;
}
From mentioned sample in comments
I'm trying to display GPS coordinates in a canvas.
I have 400 towns in France + USA. I just want to display them in a canvas.
For each town I have its latitude and longitude (taken with google api : https://developers.google.com/maps/documentation/geocoding/ ).
I've managed to have a first result using this thread : Converting from longitude\latitude to Cartesian coordinates
Here is my result :
My code is the following (assuming "_liste" contains a list of Towns) :
private void Button_visualise_Click(object sender, RoutedEventArgs e)
{
System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
watch.Start();
List<Town> _list = LibDAO.Towns_DAO.GetAllTown().Where(u=>u.IdPays == 2 || u.IdPays == 1).ToList<Town>();
this.my_canvas.Children.Clear();
double min_x = 0;
double max_x = 0;
double min_y = 0;
double max_y = 0;
for (int i = 0; i < _list .Count; i++)
{
Ellipse ell = new Ellipse() { Width = 30, Height = 30, Fill = Brushes.Blue };
Point p = ToCanvas(_list [i].Latitude, _list [i].Longitude);
Canvas.SetLeft(ell, p.X);
Canvas.SetTop(ell, p.Y);
if (p.X < min_x) min_x = p.X;
if (p.X > max_x) max_x = p.X;
if (p.Y < min_y) min_y = p.Y;
if (p.Y > max_y) max_y = p.Y;
this.my_canvas.Children.Add(ell);
}
SetCoordinateSystem(this.my_canvas, min_x, max_x, min_y, max_y);
watch.Stop();
}
public static Canvas SetCoordinateSystem(Canvas canvas, Double xMin, Double xMax, Double yMin, Double yMax)
{
var width = xMax - xMin;
var height = yMax - yMin;
var translateX = -xMin;
var translateY = height + yMin;
var group = new TransformGroup();
group.Children.Add(new TranslateTransform(translateX, -translateY));
group.Children.Add(new ScaleTransform(canvas.ActualWidth / width, canvas.ActualHeight / -height));
canvas.RenderTransform = group;
return canvas;
}
private const int earthRadius = 6367;
private Point ToCanvas(double lat, double lon)
{
lon = ConvertToRadians(lon);
lat = ConvertToRadians(lat);
double x = earthRadius * Math.Cos(lat) * Math.Cos(lon); //((lon * my_canvas.ActualWidth) / 360.0) - 180.0; ;// //
double y = earthRadius * Math.Cos(lat) * Math.Sin(lon);// ((lat * my_canvas.ActualHeight) / 180.0) - 90.0;
return new Point(x, y);
}
public double ConvertToRadians(double angle)
{
return (Math.PI / 180) * angle;
}
As you can see in my result it's almost perfect, we can recognize the 2 countries but why are they in the wrong place ? (I would like to have them like this : http://geology.com/world/world-map.gif
Am I missing something ?
Based on this link, my result may be displayed with "Azimuthal (projections onto a plane)" while I would prefer this : http://en.wikipedia.org/wiki/Equirectangular_projection
So I'm thinking of changing my function "ToCanvas" but I don't know the component "the standard parallels (north and south of the equator) where the scale of the projection is true;"
Bonus question : What's the best way to get the border from a country in GPS coordinates ? I would like to draw the border of USA for example, so I guessed I could just get coordinates of borders to draw the country.
I've tried several websites, managed to get a .shp file from there : http://www.naturalearthdata.com/downloads/110m-cultural-vectors/, but I didn't manage to retrieve borders coordinates from there.
Thank you
EDIT Seems better with this code :
private Point ToCanvas(double lat, double lon)
{
// Equirectangular projection
double x1 = lon * Math.Cos(ConvertToRadians(lat));
double y1 = lat;
//lon = ConvertToRadians(lon);
//lat = ConvertToRadians(lat);
//double x = earthRadius * Math.Cos(lat) * Math.Cos(lon); //((lon * my_canvas.ActualWidth) / 360.0) - 180.0; ;// //
//double y = earthRadius * Math.Cos(lat) * Math.Sin(lon);// ((lat * my_canvas.ActualHeight) / 180.0) - 90.0;
return new Point(x1 * 10, y1 * 10);
}