i have a picturebox that contain an image (1280 X 720), i want to create a second picturebox that contain a zoomed version of the image centered around the cursor (for example a 40 X 40 square around the cursor zomed to be in a 120 X 120 square picturebox) that follow the cursor in real time in real time (if it's possible also to have a cross in the middle of the picturebox that show the precise placing of the cursor is even better).
private void Button1_Click(object sender, EventArgs e)
{
openFileDialog1.Filter = "All jpg files (*.jpg)|*.jpg";
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
Bitmap img = new Bitmap(openFileDialog1.FileName);
double imageHeight = img.Height;
double imageWidth = img.Width;
pictureBox1.Image = img;
}
}
private void PictureBox1_MouseMove(object sender, MouseEventArgs e)
{
int xupleft = e.X - 20;
int yupleft = e.Y - 20;
Rectangle myrectangle = new Rectangle(xupleft, yupleft, 40, 40);
pictureBox2.Image = pictureBox1.Image;
}
Here is a simple example with the layout I described in the comment:
Both PictureBoxes are nested in Panels.
The first one is in Zoom mode and upon loading a file its size is adapted to avoid blank stripes to the sides. Its parent panel is used to reset to the maximum allowed size.
The 2nd one is in AutoSize mode and its parent panel is used to 1) hide the outer portions and 2) to calculate the offset to center the image.
Here is the simple Paint event for pbox1:
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
Size sz = pictureBox1.ClientSize;
Point pt = pictureBox1.PointToClient(Control.MousePosition);
e.Graphics.DrawLine(Pens.OrangeRed, pt.X, 0, pt.X, sz.Height);
e.Graphics.DrawLine(Pens.OrangeRed, 0, pt.Y, sz.Width, pt.Y);
}
Here is the MouseMove the triggers the Paint and moves pbox2:
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
pictureBox1.Invalidate();
float f = 1f * pictureBox2.ClientSize.Width / pictureBox1.ClientSize.Width;
Size s2 = pictureBox2.Parent.ClientSize;
Point p2 = Point.Round(new PointF(s2.Width/2 - e.X * f , s2.Height/2 - e.Y * f ));
pictureBox2.Location = p2;
}
The file loading is a bit tricky, as it needs to analyze the aspect ratios of image and pbox:
void loadFile(string fileName)
{
if (File.Exists(fileName))
{
pictureBox1.Size = panel1.ClientSize;
pictureBox1.Location = Point.Empty;
pictureBox1.Image = Image.FromFile(fileName);
pictureBox2.Image = Image.FromFile(fileName);
pictureBox2.Location = Point.Empty;
}
Size csz = pictureBox1.ClientSize;
Size isz = pictureBox1.Image.Size;
float iar = 1f * isz.Width / isz.Height; // aspect..
float car = 1f * csz.Width / csz.Height; //..ratios
if (iar < car)
{
pictureBox1.ClientSize = new Size((int)(pictureBox1.ClientSize.Height * iar),
pictureBox1.ClientSize.Height);
}
else if (iar > car)
{
pictureBox1.ClientSize = new Size((pictureBox1.ClientSize.Width,
(int)(pictureBox1.ClientSize.Width / iar));
}
}
Note that before loading the new images one should Dispose of the previous images! Also that after setting the pbox2.SizeMode to Autosize one could set it to Zoom and scale its Size up or down to zoom in or out, if one keeps the aspect ratio the same.
Result:
Related
I'm using WinForms. In my form i have a picturebox that i want to zoom in and out using the track bar. My picturebox is set to zoom-mode. I want the image and picturebox to be proportion height/width when i drag the bar. How can i accomplish this?
private void Open_btn_Click(object sender, EventArgs e)
{
OpenFileDialog openFileDialog1 = new OpenFileDialog();
if(openFileDialog1.ShowDialog() == DialogResult.OK)
{
Image bmp;
bmp = new Bitmap(openFileDialog1.FileName);
if (bmp == null)
{
MessageBox.Show("Loading image failed", "Error", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
}
else
{
pictureBox1.Image = bmp;
openFileDialog1.Dispose();
}
}
}
private void zoomSlider_Scroll(object sender, EventArgs e)
{
if(TrackBar1.Value == 1)
{
pictureBox1.Height += 50;
pictureBox1.Width += 50;
}
if(TrackBar1.Value == 2)
{
pictureBox1.Height += 100;
pictureBox1.Width += 100;
}
if(TrackBar1.Value == 3)
{
pictureBox1.Height += 200;
pictureBox1.Width += 200;
}
//This is not exactly what i had in mind...
}
When the form is originally created you have to save the size
Form1 : Form
{
private Size _pictOriginalSize;
Form1()
{
InitialiseComponent();
_pictOriginalSize = pictureBox1.Size;
zoomSlider.Minimum = 0;
zoomSldier.Maximum = 1000;
...
}
Now you know what it's unzoomed size was.
Next you will need to you will need to convert the value of the slider into into a scale factor.
private void zoomSlider_Scroll(object sender, EventArgs e)
{
const double MaxScale = 5.0; // The scale factor when the is at it's max
double scale = Math.Pow(MaxScale, TrackBar1.Value / TrackBar1.Maximum);
Size newSize = new Size((int) (_pictOrignalSize.Width * scale),
(int) (_pictOrignalSize.Height * scale));
pictureBox.Size = newSize;
}
I am using the Math.Pow function to convert the scale from 1 to 5 on an exponential scale - you may want to use a different technique you could consider
having the slider have values 1 to 5 and the Value simply becomes the scale: scale = zoomSlider.Value - but this only gives you a resoultion of 1.
having the slider have values 1 to 5000 and do a double division double scale = zoomSlider.Value / 1000.0; This gives you a 1000 different resolutions.
NB: I am using 5 as an example only - you can use any value for the max scale factor.
so am implementing a project that can read image pan it, zoom it and do other stuff.. everything was going well until i tried implementing a draw with right mouse button.
the problem is when i draw a line, the line that appears on the image does not correspond to the line i drew on screen, meaning its shifted and i know its because of the re-sizing and zooming of the image, but when i draw lines on the image with its original size(the image) and with panning also ; i have no problem.
here's the code.
so first here is how i load the image when i click browse and select image
Myimage = new Bitmap(ImagePath);
resized = myImage.Size;
imageResize();
pictureBox.Paint += new System.Windows.Forms.PaintEventHandler(this.pictureBox_Paint);
pictureBox.Invalidate();
the imageResize function does the following:
void imageResize()
{
//calculated the size to fit the control i will draw the image on
resized.Height = someMath;
resized.Width = someMath;
}
then in the event handler for the pictureBox_Paint event i wrote:
private void pictureBox_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
// Create a local version of the graphics object for the PictureBox.
Graphics PboxGraphics = e.Graphics;
PboxGraphics.DrawImage(myImage, imageULcorner.X, imageULcorner.Y, resized.Width, resized.Height);
}
as you can see the resized size is not the original image size i did this because i wanted the image to show on the picturebox control centralized and filled now the next part IS WHERE MY PROBLEM BEGINS
i have to draw lines on image using right mouse button so i implemented pictureBox_MouseDown & pictureBox_MouseUp event handlers
// mouse down event handler
private void pictureBox_MouseDown(object sender, MouseEventArgs e)
{
else if (mouse.Button == MouseButtons.Right)
{
mouseDown = mouse.Location;
mouseDown.X = mouseDown.X - imageULcorner.X;
mouseDown.Y = mouseDown.Y - imageULcorner.Y;
draw = true;
}
}
here is the mouse up event handler
//Mouse UP
private void pictureBox_MouseUp(object sender, MouseEventArgs e)
{
else if (mouse.Button == MouseButtons.Right)
{
if (draw)
{
mouseLocationNow.X = mouse.X - imageULcorner.X;
mouseLocationNow.Y = mouse.Y - imageULcorner.Y;
//
// get graphics object of the image ( the original not the resized)
// as the resized image only appears when i draw on the graphics of the
// pictureBox control
// i know the problem lies here but how can i fix it
//
Graphics image = Graphics.FromImage(myImage);
Pen pen = new Pen(Color.Red, 2);
image.DrawLine(pen, mouseLocationNow, mouseDown);
pictureBox.Invalidate();
}
draw = false;
}
so in the end i want to be able to draw on the re-sized image and make it correspond to the real image and also to the screen where i draw the line
thanks and sorry for the long post but this problem has been driving me crazy.
Here is a PictureBox subclass that supports the ability to apply zooming not only to the Image but also to graphics you draw onto its surface.
It includes a SetZoom function to zoom in by scaling both itself and a Matrix.
It also has a ScalePoint function you can use to calculate the unscaled coordinates from the pixel coordinates you receive in the mouse events.
The idea is to use a Transformation Matrix to scale any pixels the Graphics object will draw in the Paint event.
I include a little code for the form for testing.
public partial class ScaledPictureBox : PictureBox
{
public Matrix ScaleM { get; set; }
float Zoom { get; set; }
Size ImgSize { get; set; }
public ScaledPictureBox()
{
InitializeComponent();
ScaleM = new Matrix();
SizeMode = PictureBoxSizeMode.Zoom;
}
public void InitImage()
{
if (Image != null)
{
ImgSize = Image.Size;
Size = ImgSize;
SetZoom(100);
}
}
public void SetZoom(float zoomfactor)
{
if (zoomfactor <= 0) throw new Exception("Zoom must be positive");
float oldZoom = Zoom;
Zoom = zoomfactor / 100f;
ScaleM.Reset();
ScaleM.Scale(Zoom , Zoom );
if (ImgSize != Size.Empty) Size = new Size((int)(ImgSize.Width * Zoom),
(int)(ImgSize.Height * Zoom));
}
public PointF ScalePoint(PointF pt)
{ return new PointF(pt.X / Zoom , pt.Y / Zoom ); }
}
Here is the code in the Form that does the testing:
public List<PointF> somePoints = new List<PointF>();
private void scaledPictureBox1_MouseClick(object sender, MouseEventArgs e)
{
somePoints.Add(scaledPictureBox1.ScalePoint(e.Location) );
scaledPictureBox1.Invalidate();
}
private void scaledPictureBox1_Paint(object sender, PaintEventArgs e)
{
// here we apply the scaling matrix to the graphics object:
e.Graphics.MultiplyTransform(scaledPictureBox1.ScaleM);
using (Pen pen = new Pen(Color.Red, 10f))
{
PointF center = new PointF(scaledPictureBox1.Width / 2f,
scaledPictureBox1.Height / 2f);
center = scaledPictureBox1.ScalePoint(center);
foreach (PointF pt in somePoints)
{
DrawPoint(e.Graphics, pt, pen);
e.Graphics.DrawLine(Pens.Yellow, center, pt);
}
}
}
public void DrawPoint(Graphics G, PointF pt, Pen pen)
{
using (SolidBrush brush = new SolidBrush(pen.Color))
{
float pw = pen.Width;
float pr = pw / 2f;
G.FillEllipse(brush, new RectangleF(pt.X - pr, pt.Y - pr, pw, pw));
}
}
Here are the results after drawing a few points showing the same points in four different zoom settings; the ScaledPictureBox is obviously placed in an AutoScroll-Panel. The lines show how to use the regular drawing commands..
I'm drawing a line in a picturebox this way:
horizontalstart = new Point(0, e.Y); //Start point of Horizontal line.
horizontalend = new Point(picbox_mpr.Width, e.Y); //End point of Horizontal line.
verticalstart = new Point(e.X, 0); //Start point of Vertical line
verticalend = new Point(e.X, picbox_mpr.Height); //End point of Vertical line.
Then on the paint event I do this:
e.Graphics.DrawLine(redline, horizontalstart, horizontalend); //Draw Horizontal line.
e.Graphics.DrawLine(redline, verticalstart, verticalend); //Draw Vertical line.
Pretty simple, now, my image can zoom and here's where I struggle..
How do I keep the line in the same spot that was drawn even if I zoom the image?
Instead of storing an absolute integer coordinate, store a decimal value representing the "percentage" of that coord with respect to the width/height of the image. So if the X value was 10 and the width was 100, you store 0.1. Let's say the image was zoomed and it was now 300 wide. The 0.1 would now translate to 0.1 * 300 = 30. You can store the "percentage" X,Y pairs in PointF() instead of Point().
Here's a quick example to play with:
public partial class Form1 : Form
{
private List<Tuple<PointF, PointF>> Points = new List<Tuple<PointF, PointF>>();
public Form1()
{
InitializeComponent();
this.Shown += new EventHandler(Form1_Shown);
this.pictureBox1.BackColor = Color.Red;
this.pictureBox1.SizeChanged += new EventHandler(pictureBox1_SizeChanged);
this.pictureBox1.Paint += new PaintEventHandler(pictureBox1_Paint);
}
void Form1_Shown(object sender, EventArgs e)
{
// convert absolute points:
Point ptStart = new Point(100, 25);
Point ptEnd = new Point(300, 75);
// to percentages:
PointF ptFstart = new PointF((float)ptStart.X / (float)pictureBox1.Width, (float)ptStart.Y / (float)pictureBox1.Height);
PointF ptFend = new PointF((float)ptEnd.X / (float)pictureBox1.Width, (float)ptEnd.Y / (float)pictureBox1.Height);
// add the percentage point to our list:
Points.Add(new Tuple<PointF, PointF>(ptFstart, ptFend));
pictureBox1.Refresh();
}
private void button1_Click(object sender, EventArgs e)
{
// increase the size of the picturebox
// watch how the line(s) change with the changed picturebox
pictureBox1.Size = new Size(pictureBox1.Width + 50, pictureBox1.Height + 50);
}
void pictureBox1_SizeChanged(object sender, EventArgs e)
{
pictureBox1.Refresh();
}
void pictureBox1_Paint(object sender, PaintEventArgs e)
{
foreach (Tuple<PointF, PointF> tup in Points)
{
// convert the percentages back to absolute coord based on the current size:
Point ptStart = new Point((int)(tup.Item1.X * pictureBox1.Width), (int)(tup.Item1.Y * pictureBox1.Height));
Point ptEnd = new Point((int)(tup.Item2.X * pictureBox1.Width), (int)(tup.Item2.Y * pictureBox1.Height));
e.Graphics.DrawLine(Pens.Black, ptStart, ptEnd);
}
}
}
I loaded the jpg to picture box (in SizeMode as Zoom).
I drew a rectangle on the picture box and took the coordinate.
I opened the jpg in paint and observed the coordinate (where I drew the rectangle on the picture box).
When I compared the rectangle coordinates (x and y) with paint coordinates, they were not the same.
I changed the SizeMode to Normal and observed that the coordinates became the same, but the image size was too large so it was display partially, so I want to use Zoom SizeMode property.
Say image with size 2825x3538 and keep the picture box size mode as Normal, the image shows partially in picture box. So I changed picture box mode to Zoom (to fit the System screen resolution), and the coordinates missmatched when comparing it with Normal mode with SizeMode.
How can I achieve the same coordinates?
private void openToolStripMenuItem_Click(object sender, EventArgs e)
{
OpenFD.FileName = "";
OpenFD.Title = "open image";
OpenFD.InitialDirectory = "C";
OpenFD.Filter = "JPEG|*.jpg|Bmp|*.bmp|All Files|*.*.*";
if (OpenFD.ShowDialog() == DialogResult.OK)
{
file = OpenFD.FileName;
image = Image.FromFile(file);
pictureBox1.Image = image;
svc = Screen.PrimaryScreen;
pictureBox1.Width = svc.Bounds.Width;
pictureBox1.Height = svc.Bounds.Height - 100;
mybitmap1 = new Bitmap(pictureBox1.Image);
mybitmap1.SetResolution(300, 300);
pictureBox1.Image = mybitmap1;
}
}
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
if (mybitmap == null)
{
mybitmap = new Bitmap(pictureBox1.Width, pictureBox1.Height);
mybitmap.SetResolution(300, 300);
}
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
using (g = Graphics.FromImage(mybitmap))
{
using (Pen pen = new Pen(Color.Green, m))
{
e.Graphics.DrawRectangle(pen, r);
e.Graphics.DrawString(lab[c].ToString(), new Font(lab[c].ToString(), 8F), new SolidBrush(label1.ForeColor), r);
}
}
}
You can use two scale factors between the actual image and the picture box, one with height and the other with width.
I have an image .I want to crop 10 px from left and 10px from right of the image.I used the below code to do so
string oldImagePath="D:\\RD\\dotnet\\Images\\photo1.jpg";
Bitmap myOriginalImage = (Bitmap)Bitmap.FromFile(oldImagePath);
int newWidth = myOriginalImage.Width;
int newHeight = myOriginalImage.Height;
Rectangle cropArea = new Rectangle(10,0, newWidth-10, newHeight);
Bitmap target = new Bitmap(cropArea.Width, cropArea.Height);
using (Graphics g = Graphics.FromImage(target))
{
g.DrawImage(myOriginalImage,cropArea);
}
target.Save("D:\\RD\\dotnet\\Images\\test.jpg");
But this is not giving me the results which i expect. This outputs an image which has 10 px cropped from the right and a resized image.Instead of cropiing it is resizing the width i think.So the image is shrinked(by width). Can any one correct me ? Thanks in advance
Your new width should be reduced by twice the crop margin, since you'll be chopping off that amount from both sides.
Next, when drawing the image into the new one, draw it at a negative offset. This causes the area that you aren't interested in to be clipped off.
int cropX = 10;
Bitmap target = new Bitmap(myOriginalImage.Width - 2*cropX, myOriginalImage.Height);
using (Graphics g = Graphics.FromImage(target))
{
g.DrawImage(myOriginalImage, -cropX, 0);
}
My guess is this line
Rectangle cropArea = new Rectangle(10,0, newWidth-10, newHeight);
should be
Rectangle cropArea = new Rectangle(10,0, newWidth-20, newHeight);
Set the width of the new rectangle to be 20 less than the original - 10 for each side.
Some indication what result it is giving you would be helpful in confirming this.
Corey Ross is correct. Alternately, you can translate along the negative x axis and render at 0.0, 0.0. Should produce identical results.
using (Graphics g = Graphics.FromImage(target))
{
g.TranslateTransform(-cropX, 0.0f);
g.DrawImage(myOriginalImage, 0.0f, 0.0f);
}
You need to use the overload that has you specify both Destination Rectangle, and Source Rectangle.
Below is an interactive form of this using a picture box on a form. It allows you to drag the image around. I suggest making the picture box 100 x 100 and have a much larger image such as a full screen window you've captured with alt-prtscr.
class Form1 : Form
{
// ...
Image i = new Bitmap(#"C:\Users\jasond\Pictures\foo.bmp");
Point lastLocation = Point.Empty;
Size delta = Size.Empty;
Point drawLocation = Point.Empty;
bool dragging = false;
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
if (!dragging)
{
lastLocation = e.Location;
dragging = true;
}
delta = new Size(lastLocation.X - e.Location.X, lastLocation.Y - e.Location.Y);
lastLocation = e.Location;
if (!delta.IsEmpty)
{
drawLocation.X += delta.Width;
drawLocation.Y += delta.Height;
pictureBox1.Invalidate();
}
}
else
{
dragging = false;
}
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
Rectangle source = new Rectangle(drawLocation,pictureBox1.ClientRectangle.Size);
e.Graphics.DrawImage(i,pictureBox1.ClientRectangle,source, GraphicsUnit.Pixel);
}
//...
Okay, I totally fail at explaining this, but hang on:
The DrawImage function requires the location of the image, as well as it's position. You need a second position for cropping as how the old relates to the new, not vice versa.
That was entirely incomprehensible, but here is the code.
g.DrawImage(myOriginalImage, -cropArea.X, -cropArea.Y);
I hope that explains it more then I did.