I need to draw sin(x)/x graphic into PictureBox in animation mode by timer component. I have axes already on my picBox and graphic draws from 0;0. Also I have some code from this forum, but there my graphic draws FROM RIGHT TO THE LEFT, and I need to draw it FROM LEFT TO THE RIGHT. And I need to draw it in animation mode by timer. Could anybody help me?
Here's my drawing function:
private void drawStream()
{
const int scaleX = 35;
const int scaleY = 35;
Point picBoxTopLeft = new Point(0, 0);
Point picBoxTopLeftm1 = new Point(-1, 0);
int halfX = picBox.Width / 2;
int halfY = picBox.Height / 2;
Size size = new Size(halfX + 20, picBox.Height);
Graphics gr = picBox.CreateGraphics();
gr.TranslateTransform(halfX, halfY);
gr.ScaleTransform(scaleX, scaleY);
gr.ResetClip();
float lastY = (float)Math.Sin(0);
float y = lastY;
Pen p = new Pen(Color.Red, 0.015F);
float stepX = 1F / scaleX;
for (float x = 0; x < 15; x += stepX)
{
gr.CopyFromScreen(picBox.PointToScreen(picBoxTopLeft), picBoxTopLeftm1, size, CopyPixelOperation.SourceCopy);
y = (float)Math.Sin(x);
gr.DrawLine(p, -stepX, lastY, 0, y);
lastY = y;
}
}
Thanx a lot.
P.S. Sorry for my English, I'm Ukrainian.
It looks like the line:
gr.DrawLine(p, -stepX, lastY, 0, y);
Will always draw a line from (-stepX, lastY) to (0, y). Only the Y coordinates of these points change during your loop which doesn't look like what you want.
Moreover, you're stepping in the X direction by stepX which is defined as being 1 / 35.0f. This means you're stepping 35 times per whole pixel; a bit excessive. Get rid of your ScaleTransform and instead scale your independent variable (x) to get a more sensible frequency. You should probably also increase your amplitude to get a good looking curve as well.
I think your drawing loop should look more like:
for (int x = 1; x < halfX; x += 1)
{
y = (float) amplitude * Math.Sin(x * stepX);
gr.DrawLine(p, x - 1, lastY, x, y);
lastY = y;
}
This will draw from the origin (0,0) to the right side of the picture box at once. To animate this you're going to need to take this code and instead of looping all the way to halfX you want to loop only part of the way and keep track of where you the next time your Timer fires it's event.
edit:
Every time you create a Pen object you take a handle from Windows through GDI. These handles are only returned to be reused when you Dispose() the Pen object. If you create a new Pen every time you draw and don't dispose them you'll run out of handles eventually!
To be safe when using these types of objects (Pen, Brush, Font, and more need to be disposed) wrap them in a using statement:
using (Pen pen = new Pen(Color.Red, 0.015f)) {
// ... use the pen here
}
// After here it is Disposed and cannot be accessed
Ok, spent 20 minutes to try it myself.
public Form1()
{
InitializeComponent();
_bitmap = new Bitmap(pictureBox1.Width, pictureBox1.Height);
pictureBox1.Image = _bitmap;
Timer timer = new Timer();
timer.Tick += new EventHandler(timer_Tick);
timer.Interval = 10;
timer.Start();
}
private int x = 0;
void timer_Tick(object sender, EventArgs e)
{
if(++x < _bitmap.Width)
{
int y = _bitmap.Height/2
- ((int)(Math.Sin(x * (Math.PI * 2) / _bitmap.Width) * (_bitmap.Height / 2)));
//Small rounding error
if (y > _bitmap.Height - 1)
y = _bitmap.Height - 1;
_bitmap.SetPixel(x, y, Color.Black);
}
pictureBox1.Image = _bitmap;
}
Tested and working.
You should really add Points to a GraphicsPath and just paint that.
Also: You should not create a Graphics object like that. Get one from the Bitmap or use the one supplied by the Paint event.
1) Right to left. Just change your for loop.
for (float x = 0; x < 15; x += stepX)
becomes
for (float x = size.Width; x >= 0; x
-= stepX)
2) A timer.
Simple solution is to just add a sleep.
for (float x = 0; x < 15; x += stepX)
Thread.Sleep(500); //draw...
Related
At the moment I develope a ChartControl and it works just pretty well in my opinion,
but now I'm at a point where it would be nice to have the ability to zoom the drawed signal for better analyzing.
At the moment I calculate the needed points like this:
for (int i = 0; i < PointsCount; i++){
xAxisPoint = xAxisOP.X + i * (xAxisWidth / PointsCount);
yAxisPoint = yAxisHeight * data[i].Point / Divisor;
if(yAxisPoint > yAxisHeight){
yAxisPoint = yAxisHeight;
}
if(yAxisPoint < -yAxisHeight){
yAxisPoint = -yAxisHeight;
}
Points[i] = new PointF(xAxisPoint, yAxisOP.Y + yAxisPoint);
}
if(zoom){
graphics.ScaleTransform(0.2f*ZoomFactor, 0.2f*ZoomFactor);
}
using (Pen plotPen = new Pen(plotColor, 1)){
graphics.DrawLines(plotPen, Points);
}
But the problem is: When it zooms in, the zoom is way too big and is drawn outside the bounds of my control.
Is there a way to specify an area in which it should be Scaled (zoomed)?
For the final question: Is there a way to specify an area in which it should be scaled/zoomed? you need a combination of SetClip, TranslateTransform and ScaleTransform.
Here is an example.
It uses a
target rectangle zoomTgtArea where the zoomed graphics are displayed,
a mouse location zoomOrigin where the zoom origin is,
a float zoomFactor, a positive float.
Initial values:
Rectangle zoomTgtArea = new Rectangle(300, 500, 200, 200);
Point zoomOrigin = Point.Empty; // updated in MouseMove when button is pressed
float zoomFactor = 2f;
The trick to zoom in on only a part of the graphics is to display the graphics twice, once normally and once with the transformations of the Graphics object.
Let's try:
private void pictureBox_Paint(object sender, PaintEventArgs e)
{
// normal drawing
DrawStuff(e.Graphics);
// for the movable zoom we want a small correction
Rectangle cr = pictureBox.ClientRectangle;
float pcw = cr.Width / (cr.Width - ZoomTgtArea.Width / 2f) ;
float pch = cr.Height / (cr.Height - ZoomTgtArea.Height / 2f) ;
// now we prepare the graphics object; note: order matters!
e.Graphics.SetClip(zoomTgtArea );
// we can either follow the mouse or keep the output area fixed:
if (cbx_fixed.Checked)
e.Graphics.TranslateTransform( ZoomTgtArea.X - zoomCenter.X * zoomFactor,
ZoomTgtArea.Y - zoomCenter.Y * zoomFactor);
else
e.Graphics.TranslateTransform( - zoomCenter.X * zoomFactor * pcw,
- zoomCenter.Y * zoomFactor * pch);
// finally zoom
e.Graphics.ScaleTransform(zoomFactor, zoomFactor);
// and display zoomed
DrawStuff(e.Graphics);
}
The DrawStuff I used is simple:
void DrawStuff(Graphics g)
{
bool isZoomed = g.Transform.Elements[0]!= 1
|| g.Transform.OffsetX != 0 | g.Transform.OffsetY != 0;
if (isZoomed) g.Clear(Color.Gainsboro); // pick your back color
// all your drawing here!
Rectangle r = new Rectangle(10, 10, 500, 800); // some size
using (Font f = new Font("Tahoma", 11f))
g.DrawString(text, f, Brushes.DarkSlateBlue, r);
}
Its only extra is clearing the background so the normal drawing won't shine through the zoomed version..
Let's see:
I am developing a C# win form GUI for controlling a two-motor XY stage. I have drawn a 100 x 100 square grid pattern on a picturebox in which each square, when clicked, represents a coordinate that the two motors must move to. I have studied this link
PictureBox Grid and selecting individual cells when clicked on and this PictureBox- Grids and Filling in squares (Game of Life) for drawing a grid and marking the clicked positions.
Now I have to transform the series of randomly clicked points to actual movement of the two motors.
How shall I translate the click coordinates programmatically to give commands to control the motors?
I know how to move and control the motors without referring to the screen coordinates, i.e. by using eyes.
Thank you very much for your kind help.
Update1:
Hello... I think I am thinking too much in a confusing way to move the motors from one point to another despite Sebastien's great help. I wanted to try some logic below but I appreciate if somebody can enlighten me how best to implement this.
private void pictureBoxGrid_MouseClick(object sender, MouseEventArgs e)
{
//int x = e.X;
int x = cellSize * (e.X / cellSize);
int y = cellSize * (e.Y / cellSize);
int i = x / 8; // To limit the value to below 100
int j = y / 8;
// Reverse the value of fill_in[i, j] - if it was false, change to true,
// and if true change to false
fill_in[i, j] = !fill_in[i, j];
if (fill_in[i, j])
{
//Save the coordinate in a list
filledSq.Add(new Point(i, j));
using (Graphics g = Graphics.FromImage(buffer))
{
g.FillRectangle(Brushes.Black, x + 1, y + 1, 7, 7);
}
}
else
{
//Delete the coordinate in a list
filledSq.Remove(new Point(i, j));
Color customColor = SystemColors.ControlLightLight;
using (Graphics g = Graphics.FromImage(buffer))
using (SolidBrush shadowBrush = new SolidBrush(customColor))
{
g.FillRectangle(shadowBrush, x + 1, y + 1, 7, 7);
}
}
//pictureBoxGrid.BackgroundImage = buffer;
pictureBoxGrid.Invalidate();
}
private void buttonSavePoints_Click(object sender, EventArgs e)
{
// to be implemented...
}
private void buttonRun_Click(object sender, EventArgs e)
{
var noOfDots = filledSq.Count;
filledSq = filledSq.OrderBy(p => p.X).ThenBy(p => p.Y).ToList();
var motor = new Motor();
for (var i = 0; i < noOfDots; i++)
{
motor.Move(filledSq[i].X, filledSq[i].Y); //call the motor to move to X,Y here?
//do sth at each position
}
}
Since you wrote, that you know how to move the motors programmatically this answer will be more theoretical:
Each steppermotor has a predefined anglewidth per step (e.g. 1.8°).
And if you know where your Motors are (for example at a predefined starting point with limitswitches (0|0)) you can calculate where they need to be.
For the precision there are multiple factors like if you are using Belts or threaded rods.
An examplemethod could look like this:
private static float stepwidth = 1.8;
private static beltConverionPerDegree = 0.2; // Or rod
private float currentPositionX = 0;
private float currentPositionY = 0;
public Tuple<int, int> GetSteps(float x, float y) {
// calculate the position relative to the actual position (Vector between two points)
float relativeX = x - currentPositionX;
float relativeY = y - currentPositionY;
return new Tuple<int, int> (relativeX / (stepwidth * beltConverionPerDegree), relativeY / (stepwidth * beltConverionPerDegree));
}
beltConverionPerDegree means how much distance your motor moves the belt for each degree.
I want to increase the height of the curve but its left and right position should remain same. Just want to lift up from center to give it a shape like curve as height changes.
Pen blackPen = new Pen(Color.Black, 3);
// Create coordinates of rectangle to bound ellipse.
int x = 93;
int y = 136;
int width = 320;
int height = 50;
// Create start and sweep angles on ellipse.
int startAngle = 0;
int sweepAngle = -180;
// Draw arc to screen.
e.Graphics.DrawArc(blackPen, x, y, width, height, startAngle, sweepAngle);
In the most direct way your problem can be solved like this:
int change = 0;
e.Graphics.DrawArc(blackPen, x, y-change, width, height+change, startAngle, sweepAngle);
By increasing the variable change the ellipse will curve up more and more:
private void button1_Click(object sender, EventArgs e)
{
change += 10;
panel1.Invalidate();
}
But maybe you want more control over the shape? Let's have a look at the options:
Here are examples of the three 'curve' drawing methods:
Curves, Beziers & Ellipses
And here is the code to draw that image.
Please ignore the Graphics.xxxTransform calls! They only are meant to shift the curves a little bit upwards so they don't overlap too much to see them properly.
Also note that the curves in the first image are not completely convex. See the last part of the answer to see a DrawCurve call that avoids the concave segments!
The important part are the Points! And just as the comments suggest, in the third part the ellipses are being changed by making the height larger and moving the top of the Rectangle up by the same amount.
The complexity DrawArc of and DrawCurve is pretty much equal; both are controlled by four integers with a rather clear meaning: They either make one rectangle or the corners of a symmetrical triangle. (Plus one counterpoint for the convex call.)
DrawBezier is more complex, especially since the controls point(s) are not actually on the resulting curve. They can be thought of force vectors that pull the line into a curved shape and are harder to calculate.
private void panel1_Paint(object sender, PaintEventArgs e)
{
Point a = new Point(0, 200);
Point c = new Point(200, 200);
for (int i = 0; i< 10; i++)
{
e.Graphics.TranslateTransform(0, -5);
Point b = new Point(100, 50 + i * 10);
e.Graphics.DrawCurve(Pens.Maroon, new[] { a, b, c }, 0.7f);
}
e.Graphics.ResetTransform();
Point pa = new Point(250, 200);
Point pb = new Point(450, 200);
for (int i = 0; i < 10; i++)
{
e.Graphics.TranslateTransform(0, -5);
Point pc = new Point(350, 200 - i * 10);
e.Graphics.DrawBezier(Pens.ForestGreen, pa, pc, pc, pb);
}
e.Graphics.ResetTransform();
int x = 500;
int y0 = 200;
int w = 200;
for (int i = 0; i < 10; i++)
{
e.Graphics.TranslateTransform(0, -5);
Rectangle rect = new Rectangle(x, y0 - i * 10, w, 10 + i * 10);
e.Graphics.DrawArc(Pens.DarkBlue, rect, -0, -180);
}
e.Graphics.ResetTransform();
}
Notes:
The Curve (1st image) can be further controlled by the Tension parameter. The lower the tension the more pointed it gets, approaching 1f it makes the curve broader..
The Bezier curve (2nd image) is using only one control point. (Twice.) The curve gets a little pointed this way. You can make it broader and broader by using two different points the move apart little by little..
The Ellipse can't be controlled; it will always fill the bounding Rectangle.
Here is an example of varying the Curves and the Beziers:
The Curves are drawn with varying Tensions. Also I have used an overload that helps to get rid of the concave part at the start and end of the curve. The trick is to add a suitable extra point to the start and end and to tell the DrawCurve to leave out these 1st and last segments.
The simplest point to use (for both ends actually) is the counterpoint of the one at the top.
The Beziers are drawn using two control points, moving out and up a little.
Here is the code for the variations:
Point a = new Point(0, 200);
Point c = new Point(200, 200);
for (int i = 1; i < 10; i++)
{
e.Graphics.TranslateTransform(0, -5);
Point b = new Point(100, 50);
Point b0 = new Point(b.X, a.Y + (a.Y - b.Y));
e.Graphics.DrawCurve(Pens.Maroon, new[] { b0, a, b, c, b0 }, 1, 2, 0.1f * i);
}
e.Graphics.ResetTransform();
Point pa = new Point(250, 200);
Point pb = new Point(450, 200);
for (int i = 0; i < 10; i++)
{
e.Graphics.TranslateTransform(0, -5);
Point ca = new Point(350 - i * 9, 100 - i * 5);
Point cb = new Point(350 + i * 9, 100 - i * 5);
e.Graphics.DrawBezier(Pens.ForestGreen, pa, ca, cb, pb);
}
e.Graphics.ResetTransform();
here was the solution just increase value of y0 as u increase the value of
y0-i here i=20
int x = 96;
int y0 = 260;
int w = 320;
e.Graphics.TranslateTransform(0, -5);
Rectangle rect = new Rectangle(x, y0 - 20 * 10, w, 10 + 20 * 10);
e.Graphics.DrawArc(Pens.DarkBlue, rect, -0, -180);
e.Graphics.ResetTransform();
just wanna ask if How can I draw series of Numbers for every line of grid col and rows ...
can also be by use of label ...
something like this:
http://oi60.tinypic.com/aeblth.jpg
heres my code as for now for Grid of PictureBox:
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
int numOfCells = 200;
int cellSize = 5;
Pen p = new Pen(Color.Black);
for (int y = 0; y < numOfCells; ++y)
{
g.DrawLine(p, 0, y * cellSize, numOfCells * cellSize, y * cellSize);
}
for (int x = 0; x < numOfCells; ++x)
{
g.DrawLine(p, x * cellSize, 0, x * cellSize, numOfCells * cellSize);
}
}
Note that numbers in the bottom and right of chart Position and count
are depends on count and position of line of grid in picture .
thanks for the help . more power!
First you have to create a block of X and Y. In the given screen shot the block of Y is about 10 and the block of X is about 2. To create the block of axes you need maximum and minimum value of graph.
NOTE: This is not a complete example. This is just a logic to understand how you can draw graph line.
int iXMin = 0;
int iXMax = 52;
int iYMin = 49890;
int iYMax = 50000;
Then you should create the size of block.
int iXSize = 26;
int iYSize = 12;
Single fXBlock = (iXMax - iXMin) / iXSize;
Single fYBlock = (iYMax - iYMin) / iYSize;
Now, You will require a method to convert axes position into pixel position.
This method is used to get the point of axes.
Single fXPxlSize = (pic.ClientRectangle.Width / (iXMax-iXMin))); //Getting X pixle size between two value.
Single fYPxlSize = (pic.ClientRectangle.Height / (iYMax-iYMin)); //Getting Y pixle size between two value.
Now, You can point out the value in the PictureBox.
Suppose you have first value XValue=0,YValue=50000 then you can get pixel position by using following formula.
int iX = Convert.ToInt32((XValue - XMin) * fXPxlSize);
int iY = Convert.ToInt32((YValue - YMin) * fYPxlSize);
Point p = New Point(iX,iY);
Create two List then add all those points one by one into the list. First is used to draw Cyan Line and Second is used to draw Yellow Line
List<Point> lstPointsC = new List<Point>(); //Declaration should be class level.
List<Point> lstPointsY = new List<Point>(); //Declaration should be class level.
lstPointsC.Add(p);
if (lstPointsY.Count > 0)
lstPointsY.Add(new Point(iX, lstPointsY[lstPointsY.Count].Y));
lstPointsB.Add(p);
Create code for Lines.
Graphics g = pic.CreateGraphics(); //you can also use e.Graphics from pic_paint event.
g.DrawLines(Pens.Cyan, lstPointsC.ToArray());
g.DrawLines(Pens.Yellow, lstPointsY.ToArray());
This method is used to draw custom line graph in your own control or form. But, I would like to suggest you use any third party tool like Crystal Report or Devexpress Charts.
How can i simulate a spray like windows paint ? i think it create points random , what is your opinion?
Yes, I would say it colors random pixels within a certain radius of the selection point. There's also probably a time delay between the coloring of one pixel and the other, because machines today are fast enough to be able to color every possible pixel (As long as the radius is small) before you could let go of the mouse button.
Also, I think the algorithm that Paint uses can select a pixel to paint even if it already has been painted, since sometimes you can end up with a painted circle with a few unpainted pixels inside.
The pattern for spray paint would be semi-random. If you get out a can of Krylon and slowly spray a line on a wall, you end up with a wide solid line that fades out to the background with a gradient around the edges. Spray in one spot for ten seconds, and you get a big dot in the center in which the color is fully saturated, with a radial gradient to the background.
So- your variables for simulation include:
Time holding the "sprayer" (mouse button)
Motion of the "can" (mouse)
Speed of the "can" (fast moves make a light, unsaturated line. Slow moves make a thick, saturated line with a gradient)
spread pattern: is the spray focused like an airbrush, or big like a spray can?
"Distance": How far away is the "sprayer" from the "canvas"?
You have received a number of answers pointing you in the right direction to start handling the user experience of the spray effect. Based on your reponse to my comment you also need an algorithm for generating the random points within the radius.
There are a number of ways to do this, and probably the most obvious would be to use polar coordinates to select the random point and then transform the polar coordinate to a cartesian (x,y) coordinate to render the pixel. Here is a simple example of this approach. To keep things simple, I have just drawn a simple 1x1 ellipse for each point.
private Random _rnd = new Random();
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
int radius = 15;
using (Graphics g = this.CreateGraphics())
{
for (int i = 0; i < 100; ++i)
{
// Select random Polar coordinate
// where theta is a random angle between 0..2*PI
// and r is a random value between 0..radius
double theta = _rnd.NextDouble() * (Math.PI * 2);
double r = _rnd.NextDouble() * radius;
// Transform the polar coordinate to cartesian (x,y)
// and translate the center to the current mouse position
double x = e.X + Math.Cos(theta) * r;
double y = e.Y + Math.Sin(theta) * r;
g.DrawEllipse(Pens.Black, new Rectangle((int)x - 1, (int)y - 1, 1, 1));
}
}
}
Alternatively, you can randomly select x,y coordinates from the rectangle that fits the spray circle and using the circle equation r^2 = x^2 + y^2 test the point to determine if it lies inside the circle, if it does you randomly select another point and test again until you have a point that lies within the circle. Here is a quick sample of this approach
private Random _rnd = new Random();
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
int radius = 15;
int radius2 = radius * 2;
using (Graphics g = this.CreateGraphics())
{
double x;
double y;
for (int i = 0; i < 100; ++i)
{
do
{
// Randomy select x,y so that
// x falls between -radius..radius
// y falls between -radius..radius
x = (_rnd.NextDouble() * radius2) - radius;
y = (_rnd.NextDouble() * radius2) - radius;
// If x^2 + y^2 > r2 the point is outside the circle
// and a new point needs to be selected
} while ((x*x + y*y) > (radius * radius));
// Translate the point so that the center is at the mouse
// position
x += e.X;
y += e.Y;
g.DrawEllipse(Pens.Black, new Rectangle((int)x - 1, (int)y - 1, 1, 1));
}
}
}
You can create a spray pattern of various intensities by sampling some number (related to the desired intensity and spread) of polar coordinates. To do this, determine a random polar coordiate (ρ, θ) for each sample by:
ρ sampled from N(0, 1): Use a Normal (Gaussian) distribution for the distance from the exact center of your spray pattern. I don't recall if there's a normal variate generator in the .NET library. If there isn't, you can create one from a U(0, 1) generator.
θ sampled from U(0, π): Sample the angular component from the Uniform Continuous Distribution. Without loss of performance or generality, you could instead sample on U(nπ, mπ) for n < m, but U(0, π) will probably be fine for what you need.
The Cartesian coordinates of each sample are give by (Tx + Sxρ cos θ, Ty + Syρ sin θ) where (Tx, Ty) is the center of the spray pattern you want to create; Sx and Sy are the spread factors you want to have in the x and y directions respectively.
Try using the timer
public partial class Form1 : Form
{
int Radious = 5;
Random _rnd = new Random();
Timer T = new Timer();
int InterVal = 1000;
MouseEventArgs MEA = null;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
T.Tick += (O, E) =>
{
StartSpray();
};
this.MouseDown += (O, E) =>
{
MEA = E;
T.Interval = InterVal;
T.Start();
};
this.MouseUp += (O, E) =>
{
T.Stop();
};
}
private void StartSpray()
{
Point P = DrawPoint(Radious, MEA.X, MEA.Y);
// Draw the point on any graphics area you can add the color or anything else
}
private Point DrawPoint(int Radious, int StatX, int StartY)
{
double theta = _rnd.NextDouble() * (Math.PI * 2);
double r = _rnd.NextDouble() * Radious;
Point P = new Point { X = StatX + Convert.ToInt32(Math.Cos(theta) * r), Y = StartY + Convert.ToInt32(Math.Sin(theta) * r) };
return P;
}
}
please modify the Interval and the radius.
I think it's hard to find a sample on C#. Below I present a way to start your journey on this. Here I am using a texture brush.
private void Button1_Click(System.Object sender, System.EventArgs e)
{
try
{
Bitmap image1 = (Bitmap)Image.FromFile(#"C:\temp\mybrush.bmp", true);
TextureBrush t = new TextureBrush(image1);
t.WrapMode = System.Drawing.Drawing2D.WrapMode.Tile;
Graphics formGraphics = this.CreateGraphics();
formGraphics.FillEllipse(t, new RectangleF(90.0F, 110.0F, 100, 100));
formGraphics.Dispose();
}
catch (System.IO.FileNotFoundException)
{
MessageBox.Show("Image file not found!");
}
}
as Jesse said, I think you should find an algorithm to spread random pixels.