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.
Related
I've started using LightningChart in my real time monitoring application. In my app there are many y axis which use segmented layout (one y axis per segment):
mainChart.ViewXY.AxisLayout.YAxesLayout = YAxesLayout.Segmented;
My goal is that when you mouse click a segment, it gets larger compared to other segments (kinda like zoom effect) and the other segments gets smaller. When you click it again it goes back to normal.
I know I can change the size of the segments with:
mainChart.ViewXY.AxisLayout.Segments[segmentNumber].Height = someValue;
That takes care of the zooming effect.
Now the problem is that how can I solve which segment was actually clicked? I figured out that you get mouse position via MouseClick -event (e.MousePos) but that seem to give only the screen coordinates so i'm not sure that it helps.
I'm using the LightningChart version 8.4.2
You are correct that getting mouse position via MouseClick event is the key here. The screen coordinates you get via e.GetPosition (not e.MousePos) can be converted to chart axis values with CoordToValue() -method. Then you just compare the y-coordinate to each y-axis minimum/maximum value to find out what segment was clicked. Here is an example:
_chart.MouseClick += _chart_MouseClick;
private void _chart_MouseClick(object sender, MouseButtonEventArgs e)
{
var mousePos = e.GetPosition(_chart).Y;
double axisPos = 0;
bool isWithinYRange = false;
foreach (AxisY ay in _chart.ViewXY.YAxes)
{
ay.CoordToValue((float)mousePos, out axisPos, true);
if (axisPos >= ay.Minimum && axisPos <= ay.Maximum)
{
// Segment clicked, get the index via ay.SegmentIndex;
isWithinYRange = true;
}
}
if (!isWithinYRange)
{
// Not in any segment
}
}
After finding out the segment index, you can modify its height as you described:
_chart.ViewXY.AxisLayout.Segments[0].Height = 1.5;
Note Height means segment height compared to other segments.
Hope this is helpful.
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 image with size of 1278 X 958, I am doing some image processing on that.
As the image size is big,I load it with following command in a pictureBox:
imGrayShow = cap.QueryGrayFrame();
picBOriginal.Image = imGrayShow.ToBitmap();
pictureBox size is 299 X 204 and to see my original image in pictureBox I put the
pictureBox Size Mode= StretchImage
The story starts here:
I want to use mouse-down & mouse-up event to get the location of the area that the user selects with following command:
private void picBOriginal_MouseUp(object sender, MouseEventArgs e)
{
if (stopEventMouseDrag == true)
{
endP = e.Location;
if (startP != endP)
{
stopEventMouseDrag = false;
}
}
}
private void picBOriginal_MouseDown(object sender, MouseEventArgs e)
{
if (stopEventMouseDrag == true)
{
startP = e.Location;
}
}
What I get as the startP and endP is (145,2) and (295,83) respectively; but these are the location of mouse in pictureBox, whereas I am expecting to find out the real location of mouse down and mouse down in my original Image.
( which are=> startP: 890,1 endP: 1277,879 )
how could I possibly get the REAL location of startP-endP in the original Image?
I think your math is a bit off. If you have a 1278 x 958 image and you want to scale it down to 299 pixels wide, then you need to divide everything by 1278 / 299 which is 4.27. To keep the aspect ratio the same, the width would need to be 958 / 4.27 which rounds to 224.
Then when you receive the coordinates from the mouse down & up events, you just multiply the coordinates by 4.27 to scale up the values to the original image.
startP and endP are point relative to your picture box, or relative to the screen? From what I remember, the Location in a MouseEventArgs is relative to the form, which isn't very useful.
So... It would probably end up something like this:
// Position of the mouse, relative to the upper left corner of the picture box.
Point controlRelative = myPictureBox.PointToClient(MousePosition);
// Size of the image inside the picture box
Size imageSize = myPictureBox.Image.Size;
// Size of the picture box
Size boxSize = myPictureBox.Size;
Point imagePosition = new Point((imageSize.Width / boxSize.Width) * controlRelative.X,
(imageSize.Height / boxSize.Height) * controlRelative.Y);
I am a new C# developer and I want to create a hotspot in an image that I put in my winform application. I followed the solution posted HERE, but I did not know where I should put the coordinates to make this method works:
protected override void OnMouseMove(MouseEventArgs mouseEvent)
{
string X = mouseEvent.X.ToString();
string Y = mouseEvent.Y.ToString();
}
Where should I put the coordinates? I have two coordinates (X,Y): 110, 45
Hotspot I feel should be a small rectangular area rather than just a coordinate. Suppose you want it to be a small square area of width 20 then you would write something like this:
EDIT:
Suppose you have a PictureBox on your form called PictureBox1 and you want that a small rectangle of say 20x20 size starting from Top-Left corner of the picturebox become a hotspot (i.e. when you take mouse over it you would see a HAND cursor) then on the MouSeMove event of the PictureBox write this:
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (e.X > 0 && e.X < 20 && e.Y > 0 && e.Y < 20)
this.Cursor = Cursors.Hand;
else
this.Cursor = Cursors.Default;
}
Please remember, we are just showing the Hand Cursor to denote a hotspot we have not yet handled a Click for that matter, to make it really a web kind hotspot. If you want to do something on Click, try using the MouseUp event, in the MouseUp event the same IF clause as above would give you the condition that user has clicked on the hotspot region.
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.