How can I draw a Circle with InkStroke? - c#

I have an InkCanvas. I need to a Circle draw as InkStroke.
I know, i can a Circle or Ellipse draw with InkAnalyzer, but i need
that Circle as InkStroke in InkCanvas, not in Canvas and i don't want to
the protractor use.
I need somehow a right one Circle draw.
For a straight Line developed i this code;
private void StrokeInput_StrokeEnded(InkStrokeInput sender, PointerEventArgs args)
{
List<InkPoint> points = new List<InkPoint>();
InkStrokeBuilder builder = new InkStrokeBuilder();
InkPoint pointOne = new InkPoint(new Point(line.X1, line.Y1), 0.5f);
points.Add(pointOne);
InkPoint pointTwo = new InkPoint(new Point(line.X2, line.Y2), 0.5f);
points.Add(pointTwo);
InkStroke stroke = builder.CreateStrokeFromInkPoints(points, System.Numerics.Matrix3x2.Identity);
InkDrawingAttributes ida = inkCanvas.InkPresenter.CopyDefaultDrawingAttributes();
stroke.DrawingAttributes = ida;
inkCanvas.InkPresenter.StrokeContainer.AddStroke(stroke);
}
private void StrokeInput_StrokeContinued(InkStrokeInput sender, PointerEventArgs args)
{
line.X2 = args.CurrentPoint.RawPosition.X;
line.Y2 = args.CurrentPoint.RawPosition.Y;
}
private void StrokeInput_StrokeStarted(InkStrokeInput sender, PointerEventArgs args)
{
line = new Line();
line.X1 = args.CurrentPoint.RawPosition.X;
line.Y1 = args.CurrentPoint.RawPosition.Y;
line.X2 = args.CurrentPoint.RawPosition.X;
line.Y2 = args.CurrentPoint.RawPosition.Y;
line.Stroke = new SolidColorBrush(Colors.Purple);
line.StrokeThickness = 4;
}
How can i this Code to adjust for a Circle? Or how can i draw a Circle?
Thank you,

If I understood your question correctly, you just want to use InkStroke to draw a ellipse and add these strokes of ellipse into InkCanvas. Then, you could get use these strokes for other purposes (e.g, save these strokes and use it for the next usage). If so, I could only tell you it's impossible. Because when you draw a similar ellipse on InkCanvas, you could use InkAnalyzer to convert it to ellipse, but it actually only returns four points to you. Jayden also has mentioned it on this thread: Add polygon/ellipse to inkcanvas in uwp
But as I said, if the reason of using inkstroke to draw ellipse is to save strokes for other purposes, I can give you another way. You could use win2D-UWP relevant APIs to draw ellipse on CanvasControl and save it as CanvasSvgDocument. Then, you could save it as a svg xml file. For the net usage, you could load this svg xml file by CanvasSvgDocument.LoadAsync into CanvasControl.
If you're interested in CanvasSvgDocument, you could refer to SvgExample for more details.

float stepsize = 0.05f;
float radius = 10f;
for (float i = 0; i <= 2*Math.PI; i+= stepsize )
var x = position.x + radius * Math.cos(i);
var y = position.y + radius * Math.sin(i);
// draw dot here at x,y
}
If you want lines instead of dots, draw a line from the previous xy to the current xy. (cache xy in xy_old)

Related

How can I fill the circular sector of an elliptic shape with a color gradient?

What I want to do is to create this rotating cone visual effect.
I had previously used DirectX for that.
What i have tried so far:
Even if I'm changing the thickness to 50 or more, the Arc is still not filled.
public partial class Form1 : Form
{
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
var g = e.Graphics;
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
var center = new Point(pictureBox1.Width / 2, pictureBox1.Height / 2);
var innerR = 30;
var thickness = 20;
var startAngle = 0;
var arcLength = 360;
var outerR = innerR + thickness;
var outerRect = new Rectangle
(center.X - outerR, center.Y - outerR, 2 * outerR, 2 * outerR);
var innerRect = new Rectangle
(center.X - innerR, center.Y - innerR, 2 * innerR, 2 * innerR);
using (var p = new GraphicsPath())
{
p.AddArc(outerRect, startAngle, arcLength);
p.AddArc(innerRect, startAngle + arcLength, -arcLength);
p.CloseFigure();
e.Graphics.FillPath(Brushes.Green, p);
e.Graphics.DrawPath(Pens.Green, p);
}
}
}
I want to be able to fill the arc even when the thickness is 20 or less.
Or when the value of the innerR radius changes.
The goal is to be able to fill the arc in any case.
Here's one method of drawing that cone.
It looks like a Radar sweep, so you may want to define the sweep angle and the rotation speed (how much the current rotation angle is increased based on the Timer's interval).
Using a standard System.Windows.Forms.Timer to invalidate the Canvas that contains the Image you're showing here.
The Radar contour (the external perimeter) is centered on the canvas and drawn in relation to the thickness specified (so it's always sized as the canvas bounds). It doesn't necessarily be a perfect circle, it can be elliptical (as in the image here)
The Cone section is drawn adding an Arc to a GraphicsPath and is closed drawing two lines, from the center point of the outer GraphicsPath to the starting and ending points of the Arc (I think this is a simple method to generate a curved conic figure, it can be used in different situations and lets you generate different shapes almost without calculations, see the code about this)
It's filled with a LinearGradientBrush, the section near the center has less transparency than the section near the border; adjust as required
Each time the rotation angle reaches 360°, it's reset to 0.
This is delegated to the Timer's Tick event handler
=> Built with .Net 7, but if you need to adapt it to .Net Framework, the only things to change are the syntax of the using blocks, remove the null-forgiving operator from here: canvas!.ClientRectangle and nullable reference types (e.g., change object? to just object)
public partial class SomeForm : Form {
public SomeForm() {
InitializeComponent();
radarTimer.Interval = 100;
radarTimer.Tick += RadarTimer_Tick;
}
float coneSweepAngle = 36.0f;
float coneRotationAngle = .0f;
float radarSpeed = 1.8f;
float radarThickness = 5.0f;
System.Windows.Forms.Timer radarTimer = new System.Windows.Forms.Timer();
private void RadarTimer_Tick(object? sender, EventArgs e) {
coneRotationAngle += radarSpeed;
coneRotationAngle %= 360.0f;
canvas.Invalidate();
}
private void canvas_Paint(object sender, PaintEventArgs e) {
var center = new PointF(canvas.Width / 2.0f, canvas.Height / 2.0f);
RectangleF outerRect = canvas!.ClientRectangle;
outerRect.Inflate(-(radarThickness / 2.0f), -(radarThickness / 2.0f));
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
using var pathOuter = new GraphicsPath();
using var pathInner = new GraphicsPath();
pathOuter.AddEllipse(outerRect);
pathInner.StartFigure();
pathInner.AddArc(outerRect, coneRotationAngle, coneSweepAngle);
var arcPoints = pathInner.PathPoints;
PointF first = arcPoints[0];
PointF last = arcPoints[arcPoints.Length - 1];
pathInner.AddLines(new[] { center, last, center, first });
pathInner.CloseFigure();
using var outerPen = new Pen(Color.FromArgb(100, Color.Red), radarThickness);
using var innerBrush = new LinearGradientBrush(
center, first, Color.FromArgb(200, Color.Orange), Color.FromArgb(20, Color.Orange));
e.Graphics.FillPath(innerBrush, pathInner);
e.Graphics.DrawPath(outerPen, pathOuter);
}
}
This is how it works:

How to get XY of pictureBox1 after it has been transformed

I have a list of Points that have been drawn on pictureBox1.
pictureBox1 has been transformed.
Now, I want to get XY coordinates of the point that was drawn as I hover over any drawn point.
When I hover over the pictureBox1, I am getting the XY of the pictureBox -- not a transformed XY.
Can you help me get to the transformed XY?
Thanks
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
int height = pictureBox1.ClientSize.Height / 2;
int width = pictureBox1.ClientSize.Width / 2;
//=====
//scale
//=====
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.TranslateTransform(-width, -height);
e.Graphics.ScaleTransform(2f, 2f);
//===========
//draw center
//===========
e.Graphics.DrawLine(new Pen(Color.Black, 0.5f), new Point(width - 2, height), new Point(width + 2, height));
e.Graphics.DrawLine(new Pen(Color.Black, 0.5f), new Point(width, height - 2), new Point(width, height + 2));
//===========
//draw points
//===========
foreach (var p in Points)
{
Point[] pts = new Point[] { new Point(p.X, p.Y) };
Rectangle rc = new Rectangle(pts[0], new Size(1, 1));
e.Graphics.DrawRectangle(Pens.Red, rc);
}
}
As a variation to #Vitaly's answer you can do this:
After transforming the Graphics object you can save its transformation matrix e.Graphics.Transform in a variable:
Matrix matrix = null;
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
int height = pictureBox1.ClientSize.Height / 2;
int width = pictureBox1.ClientSize.Width / 2;
//=====
//scale
//=====
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.TranslateTransform(-width, -height);
e.Graphics.ScaleTransform(2f, 2f);
matrix = e.Graphics.Transform; // save the transformation matrix!
...
This is necessary as the transfomation data are lost after the Paint event!
Note that the GraphicsState graphics.Save()&Restore() functions can't be used very well for this purpose, as it only puts the state on the stack for using it once, meaning it doesn't save these data in a persistent way.
Later you can use the Matrix and this function to either transform Points with the same matrix or reverse the transformation, e.g. for mouse coordinates:
PointF transformed(Point p0, bool forward)
{
Matrix m = matrix.Clone();
if (!forward) m.Invert();
var pt = new Point[] { p0 };
m.TransformPoints(pt);
return pt[0];
}
Now my MouseMove event shows the location both raw and re-transformed:
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
label1.Text = e.Location + " <-> " + transformed(e.Location, false) ;
}
And to test the forward transformation you could add this to the end of the Paint event:
e.Graphics.ResetTransform();
for (int i = 0; i < Points.Count; i++)
{
Point[] pts = new Point[] { Point.Round(transformed(Points[i], true)) };
Rectangle rc = new Rectangle(pts[0], new Size(19, 19));
e.Graphics.DrawRectangle(Pens.Red, rc);
}
This first clears all the transformations and then paints larger Rectangles at the same locations by calling the transformed function.
Note that this will also work with a rotated Graphics object. (Although the last test does not draw the larger rectangles rotated, just moved to the right locations.)
Also note that I return PointF for better precision when scaling with fractions. You can use Point.Round (or Point.Truncate) to get Point.
Do have a look the the Matrix.Elements: They contain the numbers you have used:
float scaleX = matrix.Elements[0];
float scaleY = matrix.Elements[3];
float transX = matrix.Elements[4];
float transY = matrix.Elements[5];
Finally: It is well worth studying the many methods of Matrix..!
You can create a Matrix with necessary transformations and apply it in pictureBox1_Paint(...) via MultiplyTransform(...):
https://msdn.microsoft.com/en-us/library/bt34tx5d(v=vs.110).aspx
Then you can use Matrix::TransformPoints(...) to get transformed XY

Drawing sets of coordinates to pixels so they mutually scale

So I have a List<object> of longitude and latitude coordinates of two points, and I need to connect the line between them. The trick is to display all of the lines within a panel so that they are scaled within the panel's dimensions (converting coordinate numbers to match the pixels) and I almost got it. However I'm confounded by some unknown problem. The code is:
int canvasWidth = panel1.Width,
canvasHeight = panel1.Height;
var minX1 = tockeKoordinate.Min(x => x.startX);
var minX2 = tockeKoordinate.Min(x => x.endX);
var minX = Math.Min(minX1, minX2);
var maxX1 = tockeKoordinate.Max(x => x.startX);
var maxX2 = tockeKoordinate.Max(x => x.endX);
var maxX = Math.Max(maxX1, maxX2);
var maxY1 = tockeKoordinate.Max(x => x.startY);
var maxY2 = tockeKoordinate.Max(x => x.endY);
var maxY = Math.Max(maxY1, maxY2);
var minY1 = tockeKoordinate.Min(x => x.startY);
var minY2 = tockeKoordinate.Min(x => x.endY);
var minY = Math.Min(minY1, minY2);
double coordinatesWidth = Math.Abs(maxX - minX),
coordinatesHeight = Math.Abs(maxY - minY);
float coefWidth = (float)coordinatesWidth / canvasWidth,
coefHeight = (float)coordinatesHeight / canvasHeight;
Basically I check the List for minimum and maximum XY coordinates, so I know what the extreme values are. Then I use a coeficient value to recalculate the coords in pixels so that are within the panel. When I use this:
drawLine(Math.Abs((float)(line.startX - minX) / coefWidth),
Math.Abs((float)(line.startY - minY) / coefHeight),
Math.Abs((float)(line.endX - maxX) / coefWidth),
Math.Abs((float)(line.endY - maxY) / coefHeight));
which is in foreach loop that iterates trough all the elements from the List . The drawline() method is as follows:
private void drawLine(float startX, float startY, float endX, float endY)
{
PointF[] points =
{
new PointF(startX, startY),
new PointF(endX, endY),
};
g.DrawLine(myPen, points[0], points[1]);
}
WHen all of this is put together, I get this picture:
I know for a fact that the "lines" should be connected and form shapes, in this case they represent roads in a suburban area.
I figured that it treats every coordinate set like it is the only one and then scales it to the panel dimensions. Actually it should scale it in reference to all of the other coordinates
It should "zoom" them out and connect with each other, because that is the way I defined the panel dimensions and everything else.
EDIT: ToW's solution did the trick, with this line of code changed to use my List:
foreach (var line in tockeKoordinate)
{
gp.AddLine((float)(line.startX), (float)(line.startY), (float)(line.endX), (float)(line.endY));
gp.CloseFigure();
}
End result when working properly:
As far as I can see your best bet would be to add all those lines to a GraphicsPath.
After it is complete you can look at its bounding rectangle and compare it to the size your Panel offers.
Then you can calculate a scale for the Graphics object to draw with and also a translation.
Finally you draw the lines with Graphics.DrawPath.
All with just 2 division on your side :-)
Here is an example:
private void panel1_Paint(object sender, PaintEventArgs e)
{
Graphics G = e.Graphics;
Random R = new Random(13);
GraphicsPath gp = new GraphicsPath();
for (int i = 0; i < 23; i++)
{
gp.AddLine(R.Next(1234), R.Next(1234), R.Next(1234), R.Next(1234));
gp.CloseFigure(); // disconnect lines
}
RectangleF rect = gp.GetBounds();
float scale = Math.Min(1f * panel1.Width / rect.Width,
1f * panel1.Height / rect.Height);
using (Pen penUnscaled = new Pen(Color.Blue, 4f))
using (Pen penScaled = new Pen(Color.Red, 4f))
{
G.Clear(Color.White);
G.DrawPath(penUnscaled, gp);
G.ScaleTransform(scale, scale);
G.TranslateTransform(-rect.X, -rect.Y);
G.DrawPath(penScaled, gp);
}
}
A few notes:
The blue lines do not fit onto the panel
The red lines are scaled down to fit
The Pen is scaled along with the rest of the Graphics but won't go under 1f.
To create connected lines do add a PointF[] or, more convenient a List<PointF>.ToArray().
I really should have used panel1.ClientSize.Width instead of panel1.Width etc..; now it is off a tiny bit at the bottom; bad boy me ;-)

Spin Control in Windows Form

I have a picturebox with an image in it. The image contains two ellipses face to face (black & blue).
What I want is to rotate the picturebox in a timer (for the effect) so the image to be "upside down" would look much more like they've changed place, which basically it's just rotating the picturebox like how the erath is moving around it's axis.
There are various kinds of rotations from a globe, depending on how you look at it.
If you look at it from above the poles it spins like a disk or a gear and you can find code for it here. This has the advantage that you can use any image and rotate it.
If you look at it from the side, facing the equator you can't easily use bitmaps, but using just two colors it will still look nice..
Here is an example of a 'globe-like' spinning rotation:
float angle = 0f;
float aSpeed = 4f; // <-- set your speed
Brush brush1 = Brushes.CadetBlue; // and your..
Brush brush2 = Brushes.DarkSlateBlue; // ..colors
private void timer1_Tick(object sender, EventArgs e)
{
angle += aSpeed;
if (angle + aSpeed > 360)
{
angle -= 360f;
Brush temp = brush1;
brush1 = brush2;
brush2 = temp;
}
pictureBox1.Invalidate();
}
private void pictureBox1_Click(object sender, EventArgs e)
{
timer1.Enabled = !timer1.Enabled;
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
Rectangle r = pictureBox1.ClientRectangle;
Rectangle r2 = r; // see below..
r.Inflate(-20, -20); // a little smaller than the panel or pBox
if (angle < 180)
{
e.Graphics.FillEllipse(brush1, r);
e.Graphics.FillPie(brush2, r, 270, 180);
r.Inflate(-(int)(r.Width * angle / 360f), 0);
e.Graphics.FillEllipse(brush2, r);
}
else
{
e.Graphics.FillEllipse(brush2, r);
e.Graphics.FillPie(brush1, r, 90, 180);
r.Inflate(-(int)(r.Width * angle / 360f), 0);
e.Graphics.FillEllipse(brush1, r);
}
}
}
This is created by three DrawXXX calls: a circle of one color and an ellipse and an arc, set to display a half circle of the same, second color.
Note: To make the angular speed uniform you may want to play with a little Math.Sin and/or an angle table..
If you look at it from any other angle and if you need to show rotating bitmaps in 3D you can't easily draw it but will need to resort to displaying frames..
But you can combine the disk rotation from the link with the code above and will get rather complex rotations, that look a lot like a 3D sphere.. Simply add the code before the drawing..
float bw2 = r2.Width / 2f;
float bh2 = r2.Height / 2f;
e.Graphics.TranslateTransform(bw2, bh2);
e.Graphics.RotateTransform(angle / 3);
e.Graphics.TranslateTransform(-bw2, -bh2);
..use the drawing from above instead of the DrawImage line and move the ResetTransform to the end. You will want to use a different or scaled angle!

WinForm Triangle animation

I am trying to make a triangle move back and forth over an arc, the triangle shoud rotate while moving.
I have made a picture to explain it better:
https://app.box.com/s/mt9p66zlmtkkgkdvtb5h
The math looks right to me, can anyone tell me what I am doing wrong?
public partial class Form1 : Form
{
bool turn = false;
double angle = 0;
public Form1()
{
InitializeComponent();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Brush solidBlackBrush = new SolidBrush(Color.Black); //En solid svart brush som brukes flere steder
Pen solidBackPen = new Pen(solidBlackBrush);//En solid svart pen som brukes flere steder
//Trekant = Norwegian for Triangle, Trekant is a class that draws a polygon shaped as a Triangle.
Trekant tre = new Trekant();
e.Graphics.DrawArc(solidBackPen, new Rectangle(new Point(50,50), new Size(100,100)) , 180, 180);
//X = a + r*Cos(angle) | Y = b + r*Sin(angle)
double x = (50+(100/2)) + (100/2) * Math.Cos(Trekant.DegreeToRadian(angle));
double y = (50+(100/2)) - (100/2) * Math.Sin(Trekant.DegreeToRadian(angle));
e.Graphics.TranslateTransform((float)x - 15, (float)y - 40);//Flytter 0 slik at pistolen havner på rett sted
e.Graphics.RotateTransform((float)-Trekant.RadianToDegree(Trekant.DegreeToRadian(angle-90)));
tre.Draw(e.Graphics);
}
private void timer1_Tick(object sender, EventArgs e)
{
if (angle == 0)
{
turn = false;
}
if (angle == 180)
{
turn = true;
}
if (turn)
{
angle -= 10;
}
if (!turn)
{
angle += 10;
}
this.Invalidate();
}
}
Without going into coding let's first set up the math..
Let say the half ellipse in the picture has a width of 2w and a height of h. And lets assume you want the movement to happen in n steps.
Then at each step s the rotation angle is s * 180f/n. The rotation point's x stays at w plus whatever offset ox the ellipse has, but will have to move its y vertically from offset oy, first by (w-h) * 2f / n down on each step and then up again by the same amounts..
The Drawing itself moves accordingly.
So you have a TranslateTransform for the rotation point, the RotateTransform, then another TranslateTransform to place the image, then the DrawImage and finally a ResetTransform.
I hope that helps. If that doesn't work, please update the question and we'll can get it right, I'm sure..

Categories