I have a canvas that changes size depending on the users input, i want to hit a button and be able to zoom out to see the whole canvas. Basically i know the height and width of the canvas i just want to zoom in or out so the user can see the whole canvas. I have zoom in and zoom out to work but not sure how do zoom to fit? any thoughts?
February 11th
Thank you for your response, This was great help and i am able to zoom out on the canvas completely. The problem that i am having now is the user has zoom in, and zoom out controls. So if the user zooms in and out and then tries to zoom to fit the canvas the scaling factor will be off, my code is below
This is for the basic zooming in and out on the canvas:
double ScaleRate = 1.2;
public void buttonZoomIn_Click(object sender, RoutedEventArgs e)
{
st.ScaleX *= ScaleRate;
st.ScaleY *= ScaleRate;
}
public void buttonZoomOut_Click(object sender, RoutedEventArgs e)
{
st.ScaleX /= ScaleRate;
st.ScaleY /= ScaleRate;
}
The zoom to fit button that i want to press to zoom out completely on the canvas:
private void zoomToFitBt_Click(object sender, RoutedEventArgs e)
{
float maxWidthScale = (float)ScrollViewerCanvas.Width / (float)Canvas1.Width;
float maxHeightScale = (float)ScrollViewerCanvas.Height / (float)Canvas1.Height;
float scale = Math.Min(maxHeightScale, maxWidthScale);
if (st.ScaleX > scale || st.ScaleY> scale)
{
st.ScaleX *= scale;
st.ScaleY *= scale;
}
}
If i press the zoom to fit button off the start it is fine, but its when the user has done some manual zooming that it messes up. My idea was to maybe every time the user hits the zoom to fit button that it will first go back to its initial state then use the zoom to fit code but not sure about that.
Thanks for your help guys
Based on what little information given, I will simplify your problem into what I gauge you are asking at a most basic mathmatical level. I think you are asking...
"I have 2 rectangles (the viewport,
and the canvas). How do I scale
the canvas such that it is as big as
possible without exceeding the width
or height of the viewport."
this Code will determine how to scale a rectangle in such a way that it will barely fit inside of another rectangle.
Rectangle c = new Rectangle(0, 0, 200, 100); //Canvas Rectancle (assume 200x100)
Rectangle v = new Rectangle(0, 0, 50, 50); //Viewport Rectangle (assume 50x50)
//The maximum scale width we could use
float maxWidthScale = (float)v.Width / (float)c.Width;
//The maximum scale height we could use
float maxHeightScale = (float)v.Height / (float)c.Height;
//Use the smaller of the 2 scales for the scaling
float scale = Math.Min(maxHeightScale, maxWidthScale);
scale = .25 (or 25%) in order to fit using the sample rectangles.
Related
I would like to write an application that will measure fragments of a specimen examined under a microscope. I thought that the best way would be to capture the image and draw on selected parts of the specimen then count the value of the drawn line in pixels (and later to convert this value into the appropriate unit).
Is there anything that helps solve such issue already implemented or any tool/package or something that allows such calculations?
I will also willingly learn about solutions in other programming languages if they allow to solve this problem in a easier way or just in some way.
This is a very basic example of measuring a segmented line drawn onto an image in winforms.
It uses a PictureBox to display the image, a Label to display the current result and for good measure I added two Buttons the clear all points and to undo/remove the last one.
I collect to pixel positions in a List<Point> :
List<Point> points = new List<Point>();
The two edit buttons are rather simple:
private void btn_Clear_Click(object sender, EventArgs e)
{
points.Clear();
pictureBox1.Invalidate();
show_Length();
}
private void btn_Undo_Click(object sender, EventArgs e)
{
if (points.Any())points.Remove(points.Last());
pictureBox1.Invalidate();
show_Length();
}
Note how I trigger the Paint event by invalidating the image whenever the points collection changes..
The rest of the code is also simple; I call a function to calculate and display the sum of all segment lengths. Note that I need at least two points before I can do that or display the first line..
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
points.Add(e.Location);
pictureBox1.Invalidate();
show_Length();
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
if (points.Count > 1) e.Graphics.DrawLines(Pens.Red, points.ToArray());
}
void show_Length()
{
lbl_len.Text = (pointsF.Count) + " point(s), no segments. " ;
if (!(points.Count > 1)) return;
double len = 0;
for (int i = 1; i < points.Count; i++)
{
len += Math.Sqrt((points[i-1].X - points[i].X) * (points[i-1].X - points[i].X)
+ (points[i-1].Y - points[i].Y) * (points[i-1].Y - points[i].Y));
}
lbl_len.Text = (points.Count-1) + " segments, " + (int) len + " pixels";
}
A few notes:
The image is displayed without any zooming. PictureBox has a SizeMode property to make zoomed display simple. In such a case I recommend to store not the direct pixel locations of the mouse but 'unzoomed' values and to use a 'rezoomed' list of values for the display. This way you can zoom in and out and still have the points stick to the right spots.
For this you ought to use a List<PointF> to keep precision.
When zooming e.g. by enlarging the PictureBox, maybe after nesting it in a Panel, make sure to either keep the aspect ratio equal to that of the Image or to do a full calculation to include the extra space left or top; in SizeMode.Normal the image will always sit flush TopLeft but in other modes it will not always do so.
For the calculation of actual i.e. physical distances simply divide by the actual dpi value.
Let's see what we have in action:
Update:
To get a chance to create cloers fits and better precision we obviously need to zoom in on the image.
Here are the necessary changes:
We add a list of 'floating points':
List<PointF> pointsF = new List<PointF>();
And use it to store the un-zoomed mouse positions in the mouse down:
pointsF.Add( scaled( e.Location, false));
We replace all other occurances of points with pointsF.
The Paint event always calculates the scaled points to the current zoom level:
if (pointsF.Count > 1)
{
points = pointsF.Select(x => Point.Round(scaled(x, true))).ToList();
e.Graphics.DrawLines(Pens.Red, points.ToArray());
}
And the function to do the scaling looks like this:
PointF scaled(PointF p, bool scaled)
{
float z = scaled ? 1f * zoom : 1f / zoom;
return new PointF(p.X * z, p.Y * z);
}
It uses a class level variable float zoom = 1f; which gets set along with the picturebox's Clientsize in the Scroll event of a trackbar:
private void trackBar1_Scroll(object sender, EventArgs e)
{
List<float> zooms = new List<float>()
{ 0.1f, 0.2f, 0.5f, 0.75f, 1f, 2, 3, 4, 6, 8, 10};
zoom = zooms[trackBar1.Value];
int w = (int)(pictureBox2.Image.Width * zoom);
int h = (int)(pictureBox2.Image.Height * zoom);
pictureBox2.ClientSize = new Size(w, h);
lbl_zoom.Text = "zoom: " + (zoom*100).ToString("0.0");
}
The picturebox is nested inside a Panel with AutoScroll on. Now we can zoom and scroll while adding segments:
I have a pictureBox2 and it is set to zoom, I am trying to find out how to to get a real x,y pixel location on the image by Mouse.Click on pictureBox2. but I tried 3 possible ideas I knew of: without/with PointToClient,PointToScreen but I can never get it right.
private void pictureBox2_Click(object sender, EventArgs e)
{
MouseEventArgs me = (MouseEventArgs)e;
txtpictureHeight.Text =(
(OriginalImage.GetImageHeight()*me.Location.Y)/ pictureBox2.Image.Height).ToString();
txtpictureWidth.Text = (
(OriginalImage.GetImageWidth()* me.Location.X)/ pictureBox2.Image.Width).ToString();
}
There must be some factor I need to take care of so I thought to use double result from above and I get closed but there is still 80px off for the height on my test image (1371x2221). As I use Zoom so there are 2 extra spaces on my pictureBox2
Note that with SizeMode set to Zoom, the PictureBox keeps aspect ratio, and centers the image, so on top of calculating the adjusted coordinates, you also have to take padding into account.
My advice, don't use the Click event; it is meant to detect button clicks, not to actually process interaction of the mouse with an object. Use MouseDown instead.
The first thing we need to do is get the width and height of the original image. As I noted in my comment, this is simply the object inside the Image property of the PictureBox.
Next, we need the dimensions and location of the zoomed image. For that, we can start from the dimensions of the ClientRectangle of the PictureBox. Divide those by the image width and height and you'll get the horizontal and vertical zoom values. If the SizeMode would be set to StretchImage, that'd be all we need, but since aspect ratio is conserved, you need the smallest of the two values to have the actual zoom factor.
Once we got that, multiply the original width and height by this zoom factor to get the zoomed width and height, then subtract that from the actual ClientRectangle dimensions and divide it by two to get the padding for both dimensions. This can of course be simplified by checking which of the two possible zoom factors is used, and only calculating the padding for the other one, since the dimension of which the zoom factor was used obviously has 0 padding.
Now you got the padding and zoom factor, the rest is simple: subtract the padding values from the mouse coordinates, and then divide both results by the zoom factor.
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
// Default check: left mouse button only
if (e.Button == MouseButtons.Left)
ShowCoords(e.X, e.Y);
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
// Allows dragging to also update the coords. Checking the button
// on a MouseMove is an easy way to detect click dragging.
if (e.Button == MouseButtons.Left)
ShowCoords(e.X, e.Y);
}
private void ShowCoords(Int32 mouseX, Int32 mouseY)
{
Int32 realW = pictureBox1.Image.Width;
Int32 realH = pictureBox1.Image.Height;
Int32 currentW = pictureBox1.ClientRectangle.Width;
Int32 currentH = pictureBox1.ClientRectangle.Height;
Double zoomW = (currentW / (Double)realW);
Double zoomH = (currentH / (Double)realH);
Double zoomActual = Math.Min(zoomW, zoomH);
Double padX = zoomActual == zoomW ? 0 : (currentW - (zoomActual * realW)) / 2;
Double padY = zoomActual == zoomH ? 0 : (currentH - (zoomActual * realH)) / 2;
Int32 realX = (Int32)((mouseX - padX) / zoomActual);
Int32 realY = (Int32)((mouseY - padY) / zoomActual);
lblPosXval.Text = realX < 0 || realX > realW ? "-" : realX.ToString();
lblPosYVal.Text = realY < 0 || realY > realH ? "-" : realY.ToString();
}
Note, I used sharp pixel zoom here to better show the effect. It's a little trick you can do by subclassing PictureBox and overriding its OnPaint method, to adjust the Graphics object from the PaintEventArgs object and set its InterpolationMode to NearestNeighbor (It's also advised to set PixelOffsetMode to Half; there's a bug where sharp zoom is shifted half a pixel unless you do that). Then you call base.OnPaint() with that adjusted event args object.
I also added some more info on it here, but that's all just stuff you can get from the in-between values of the pixel coordinates calculation process anyway.
I've seen few questions about this problem, I tried every solution but none of them worked for my case.
My code is working; this image shows what happens when I click on Draw button.
I need to zoom on that drawing.Is it possible to code something like autocad feature "zoom/extent"?
Pen myPen = new Pen(Color.Black);
int centerpointx, centerpointy;
private void pictureBoxDraw_Paint(object sender, PaintEventArgs e)
{
centerpointx = pictureBoxDraw.Size.Width/2;
centerpointy = pictureBoxDraw.Size.Height/2;
myPen.Width = 2;
if (binary > 0)
{
var sizecrestgeo = 40;
var distancearraycrestgeo = new float[sizecrestgeo];
var elevationarraycrestgeo = new float[sizecrestgeo];
for (int i = 0; i < sizecrestgeo; i++)
{
distancearraycrestgeo[i] = float.Parse(dataGridViewCrestGeo.Rows[i].Cells[0].Value.ToString());
elevationarraycrestgeo[i] = float.Parse(dataGridViewCrestGeo.Rows[i].Cells[1].Value.ToString())*-1;
}
for (int i=0; i < sizecrestgeo-1; i++)
{
e.Graphics.DrawLine(myPen, distancearraycrestgeo[i]+centerpointx, elevationarraycrestgeo[i]+centerpointy, distancearraycrestgeo[i + 1]+centerpointx, elevationarraycrestgeo[i + 1]+centerpointy);
}
}
else
{
}
}
private void buttonDraw_Click_1(object sender, EventArgs e)
{
if (Hd > 0.0001)
{
binary = 1;
pictureBoxDraw.Invalidate();
}
else
{
MessageBox.Show("No data to draw, perform analysis first.");
}
}
private void buttoncleardraw_Click(object sender, EventArgs e)
{
binary = 0;
pictureBoxDraw.Invalidate();
}
}
This is not so hard, provided you know all the puzzle pieces.
Let's start with the obvious one:
You can scale the Graphics object to create zoomed graphics with ScaleTransform.
As I mentioned, this will include the widths of pens, font sizes and also any images you draw (though not the hatches of a HatchBrush).
You also asked about keeping the drawing 'centered'. This is a non-obvious concept: Just what is the center of your drawing surface??
When zooming (just like rotating) you always need to know the center point of the zoom (or the rotation.) By default this is the origin (0,0). I chose the center of the Panel. You may want to pick some other point..
Once you do you can move the origin of the graphics viewport to this point with TranslateTransform.
Once you have achieved all this you almost certainly will want to allow scrolling.
To do so you have two options:
You can keep AutoScroll = false and nest the canvas control inside another control, usually a Panel, which has AutoScroll = true; next make the canvas control big enough to always hold your drawing and you're done.
Or you can turn on AutoScroll for the canvas control and also set a large enough AutoScrollMinSize. If you then add the current scrolling position to the translation you are also done. Let's see this solution in action:
This is the code in the Paint event:
Size sz = panel3.ClientSize;
Point center = new Point(sz.Width / 2, sz.Height / 2);
Graphics g = e.Graphics;
// center point for testing only!
g.DrawEllipse(Pens.Orange, center.X - 3, center.Y - 3, 6, 6);
// you determine the value of the zooming!
float zoom = (trackBar1.Value+1) / 3f;
// move the scrolled center to the origon
g.TranslateTransform(center.X + panel3.AutoScrollPosition.X,
center.Y + panel3.AutoScrollPosition.Y);
// scale the graphics
g.ScaleTransform(zoom, zoom);
// draw some stuff..
using(Pen pen = new Pen(Color.Yellow, 0.1f))
for (int i = -100; i < 100; i+= 10)
g.DrawEllipse(Pens.Yellow, i-22,i-22,44,44);
A few notes:
I draw an orange circle in the center to show this point is invariant.
My coordinates go from the negative to the positive so you can see that this works nicely.
I draw with a tiny pen width; so the width of the drawing only changes once the resulting pen goes over 1 pixel. Anything draw will always be draw with 1 pxiel width, though.
I first translate and then scale so I don't have to calculate scaled poitions.
The only line in the TrackBar's Scroll event is to trigger the Paint event: panel3.Invalidate();
The only settings needed for the Panel are
panel3.AutoScroll = true;
panel3.AutoScrollMinSize = new Size(500, 500); // use the size you want to allow!
However to avoid flicker it is highly recommended to use a DoubleBuffered control, maybe a Panel subclass like this:
class DrawPanel : Panel
{
public DrawPanel() { DoubleBuffered = true; }
}
Update: Instead of a Panel, which is a Container control and not really meant to draw onto you can use a Picturebox or a Label (with Autosize=false); both have the DoubleBuffered property turned on out of the box and support drawing better than Panels do.
Graphics.ScaleTransform() is how you can zoom. Try using something like this inside your paint event handler:
e.Graphics.ScaleTransform(2.0F, 2.0F);
Description
I am creating a "simulator" for different resolutions. Basically I have a control inside a panel. Then the user can choose an aspect ratio (more information below) and the control inside the simulator should get resized to match the desired ratio.
Problem
The problem is that I do not know what is a good way to calculate the size of the control without having to use a lot of CPU or trying or resizing. How can I know, if I have to resize the height or the width of the control to fit inside the simulator?
The simulator can grow or shrink. That is why I can not be sure, if the control inside of it will fill up the whole width of the simulator, or the height, but the aspect ratio should be always correct.
Hint
This is probably a very easy mathematical problem. I just don't find a solution right now (today is not my day)!
If you have any ideas or suggestions, feel free to ask and propose!
Example
Available Resolutions
3:2 (Like on the iPhone until iPhone 5; Anyone knows an other name?)
16:10 (WXGA)
16:9 (Widescreen)
4:3 (VGA)
Thanks for helping!
Here is a little testbed. Put a control on a form and assign the ViewPort variable to it. Here I chose a textBox1 but any control will do. Then pick a target aspect ratio, I chose TargetRatio_3_2.
Try it by resizing the form!
Note 1: I chose a form as the Container (your Simulator) for easier testing and therefore use its ClientRectangle to access its inner measures. Any other Control will basically work the same.
Not 2: I have added a few lines to place the ViewPort (your Control) in the center.
private void Form1_Resize(object sender, EventArgs e)
{
Control Container = this;
Control ViewPort = textBox1;
float ContainerRatio = 1f * Container.ClientRectangle.Width / Container.ClientRectangle.Height;
const float TargetRatio_3_2 = 3f / 2f;
const float TargetRatio_16_9 = 16f / 9f;
const float TargetRatio_4_3 = 4f / 3f;
const float TargetRatio_16_10 = 16f / 10f;
//..
float TargetRatio = TargetRatio_3_2;
if (ContainerRatio < TargetRatio)
{
ViewPort.Width = Container.ClientRectangle.Width;
ViewPort.Height = (int)(ViewPort.Width / TargetRatio);
ViewPort.Top = (Container.ClientRectangle.Height - ViewPort.Height) / 2;
ViewPort.Left = 0;
}
else
{
ViewPort.Height = Container.ClientRectangle.Height;
ViewPort.Width = (int)(ViewPort.Height * TargetRatio);
ViewPort.Top = 0;
ViewPort.Left = (Container.ClientRectangle.Width - ViewPort.Width) / 2;
}
}
I have a panel called "canvas". It is transparent. So the background is from the form image, which is dark blue. This shows in the panel or canvas.
When I save the canvas to image, it saves the background, but not what I have drawn thereon, my drawline pen is yellow. And I can see it drawing on the panel. But when I save it... there are not yellow lines in the image.
What am I missing? Where are my yellow lines?
I am running this with my timer tick... to get the view to update.
This tracks the position of a CNC type machine. Gives a visual of where
the machine is in relation to Zero.
My ultimate goal, is to have a "viewport" that is zoomable, thus getting it
into a image, for easy resizing, and displaying in a pictureBox, which will
handle the stretched image and center it automatically?
I have read some complex solutions, but I am after the simple ones.
Any help would be appreciated. Sincerely,
private void VMoveNow()//Draw on panel called "canvas".
{
double a = GV.MYa * -1; //Change Direction of Y Drawing.
xc = Convert.ToInt32(GV.MXa) + (canvas.Width / 2);
yc = Convert.ToInt32(a) + (canvas.Height / 2);
g = canvas.CreateGraphics();
g.DrawLine(p, x, y, xc, yc);
x = xc;
y = yc;
g.Dispose();
}
private void SaveBMP()
{
try
{
Bitmap mybmp = new Bitmap(canvas.Width, canvas.Height);
canvas.DrawToBitmap(mybmp, canvas.Bounds);
mybmp.Save("C:\\myimage.bmp");
}
catch
{
return;
}
}
Thanks for looking.
I answered my own problem, after several attempts... I have figured out that I can scale my var's used for this drawings... and the size of the Drawline will be scale as a result.
So I now have scaling of the Drawline drawing, in a panel, with no picture or picture box needed. Does what I wished.
Setting the Pen width to -1 takes care of it resizing.