How do I make sure my graphics are drawn in the right position? I have a windows form project where the user draws lines on an Image in a pictureBox (on top of a panel). When the picture box is in default zoom the lines are drawn correctly, and they respond correctly to the Image being ragged around. However when I try to draw on the image while zoomed in/out the position of line is offset (up and to left when zoomed out, down and right when zoomed in). The pictureBox and panel are anchored on all four sides and not docked. I tried using the TranslateTransform( dx, dy ) method but it didn't work. I also tried getting rid of my CenterBox() method. How do i proceed?
Here is code for zooming:
private void trackBar1_Scroll(object sender, EventArgs e) // zoom scale
{
zoom = (float)(0.25 + 0.25 * (trackBar1.Value - 1));
if (trackBar1.Value > 0)
{
pictureBox1.Image = PictureBoxZoom(imgOriginal, new Size(trackBar1.Value, trackBar1.Value));
}
}
public Image PictureBoxZoom(Image img, Size size) //creates zoomed in clone of user image
{
sizeNewx = (Int32) (img.Width * zoom);
sizeNewy = (Int32) (img.Height * zoom);
Bitmap bm = new Bitmap(img, sizeNewx,sizeNewy);
Graphics grap = Graphics.FromImage(bm);
grap.InterpolationMode = InterpolationMode.HighQualityBicubic;
CenterBox(pictureBox1, bm);
return bm;
}
private void CenterBox(PictureBox picBox, Bitmap pic)
{
picBox.Image = pic;
picBox.Location = new Point((picBox.Parent.ClientSize.Width / 2) - (pic.Width / 2),
(picBox.Parent.ClientSize.Height / 2) - (pic.Height / 2));
picBox.Refresh();
}
Here is how graphics are drawn and zoomed:
private Stack<Line> lines = new Stack<Line>();
private void pictureBox1_MouseDown(object sender, MouseEventArgs e) //click in box
{
var mouseEventArgs2 = e as MouseEventArgs;
if (e.Button == MouseButtons.Left)
{
lines.Push(new Line { Start = mouseEventArgs2.Location });
}
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (lines.Count > 0 && e.Button == System.Windows.Forms.MouseButtons.Left)
{
lines.Peek().End = e.Location;
pictureBox1.Invalidate();
}
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.ScaleTransform(zoom, zoom);
foreach (var line in lines)
{
Pen magenta = new Pen(Color.Magenta, 2);
e.Graphics.DrawLine(magenta, line.Start, line.End);
}
}
Ok found the issue. The problem is that the points you get in mouse move and mouse end are basically scaled, cause image is scaled and then in paint you scale them again. So you need to un scale them before paint:
private void pictureBox1_MouseDown(object sender, MouseEventArgs e) //click in box
{
var mouseEventArgs2 = e as MouseEventArgs;
if (e.Button == MouseButtons.Left)
{
Point[] pnts = new Point[ 1 ];
Matrix scaleMatrix = new Matrix( 1 / zoom, 0, 0, 1 / zoom, 0, 0 ); //un scale
pnts[0]= mouseEventArgs2.Location;
scaleMatrix.TransformPoints( pnts );
lines.Push(new Line { Start = pnts[0] });
}
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (lines.Count > 0 && e.Button == System.Windows.Forms.MouseButtons.Left)
{
Point[] pnts = new Point[ 1 ];
Matrix scaleMatrix = new Matrix( 1 / zoom, 0, 0, 1 / zoom, 0, 0 ); //un scale
pnts[0]= e.Location;
scaleMatrix.TransformPoints( pnts );
lines.Peek().End = pnts[0];
pictureBox1.Invalidate();
}
}
Related
So when ever I hold the left mouse button and draw the rectangle on the form.
The form itself keeps flickering.
I've tried setting the DoubleBuffered property to true, that didnt change anything.
Is there a way to change the refresh rate or something similar?
Because from what I've tried, the example above and
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
It doesnt seem to change anything
Here is a gif showing the flickering
https://i.imgur.com/Wa3kmbM.gifv
private void Form1_Load(object sender, EventArgs e)
{
//Hide the Form
this.Hide();
label1.Visible = false;
label2.Visible = false;
//Create the Bitmap (This is an a black bitmap holding just the size.) (Bitmap is mutable)
Bitmap printscreen = new Bitmap(Screen.PrimaryScreen.Bounds.Width,
Screen.PrimaryScreen.Bounds.Height);
//Create a graphics object that's ready for alteration. `printscreen` is now a graphics Object
Graphics graphics = Graphics.FromImage(printscreen);
//Alter the graphics object which again is the printscreen
graphics.CopyFromScreen(0, 0, 0, 0, printscreen.Size);
//Create a temporary memory stream for the image
using (MemoryStream s = new MemoryStream())
{
//save graphic variable into memory
printscreen.Save(s, ImageFormat.Bmp);
//Set the size of the picturebox
pictureBox1.Size = new Size(Width, Height);
//set the value of the picturebox.Image to an Image.FromStream and load the temp stream
pictureBox1.Image = Image.FromStream(s);
}
//Show Form
this.Show();
//Cross Cursor
Cursor = Cursors.Cross;
}
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
//startX is holds the corresponding value of where the mouse is in relation to the pixels of the width
//for instance, if it's all the way to the right, it holds the value of 1920.
startX = e.X;
startY = e.Y;
selectPen = new Pen(Color.DimGray, 2);
selectPen.DashStyle = DashStyle.Dash;
pictureBox1.Refresh();
start = true;
}
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (start)
{
pictureBox1.Refresh();
//selectingWidth is equal to how much we have selected from the starting point.
//Let's say the starting point is 10 and our current mouse position is at 10 then we have selected 0 pixels to the right.
//However if we move our mouse 10 pixels to the right it means we have selected 10 pixels to the right.
selectingWidth = e.X - startX;
selectingHeight = e.Y - startY;
//if the selectingWidth is less than 0, then set the origin to the current position of the mouse.
var drawfromX = selectingWidth < 0 ? e.X : startX;
var drawfromY = selectingHeight < 0 ? e.Y : startY;
pictureBox1.Refresh();
pictureBox1.CreateGraphics().DrawRectangle(selectPen,
drawfromX,
drawfromY,
Math.Abs(selectingWidth),
Math.Abs(selectingHeight));
}
}
So I am trying to recreate the Snipping Tool that comes with Windows.
However I've manage to get it to draw a rectangle from the top left corner and then when I move my cursor to the bottom right.
However if I try to move my cursor from let's say the middle of the screen to the top left it doesnt draw the rectangle.
Why is this?
Here is a GIF showing what happends
https://i.imgur.com/0Y7xXnS.gifv
//These variables control the mouse position
int selectX;
int selectY;
int selectWidth;
int selectHeight;
public Pen selectPen;
//This variable control when you start the right click
bool start;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
//Hide the Form
this.Hide();
//Create the Bitmap (This is an a black bitmap holding just the size.) (Bitmap is mutable)
Bitmap printscreen = new Bitmap(Screen.PrimaryScreen.Bounds.Width,
Screen.PrimaryScreen.Bounds.Height);
//Create a graphics object that's ready for alteration. `printscreen` is now a graphics Object
Graphics graphics = Graphics.FromImage(printscreen);
//Alter the graphics object which again is the printscreen
graphics.CopyFromScreen(0, 0, 0, 0, printscreen.Size);
//Create a temporary memory stream for the image
using (MemoryStream s = new MemoryStream())
{
//save graphic variable into memory
printscreen.Save(s, ImageFormat.Bmp);
//Set the size of the picturebox
pictureBox1.Size = new Size(Width, Height);
//set the value of the picturebox.Image to an Image.FromStream and load the temp stream
pictureBox1.Image = Image.FromStream(s);
}
//Show Form
this.Show();
//Cross Cursor
Cursor = Cursors.Cross;
}
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
selectX = e.X;
selectY = e.Y;
selectPen = new Pen(Color.DimGray, 1);
selectPen.DashStyle = DashStyle.Solid;
pictureBox1.Refresh();
start = true;
}
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (start)
{
selectWidth = e.X - selectX;
selectHeight = e.Y - selectY;
pictureBox1.Refresh();
pictureBox1.CreateGraphics().DrawRectangle(selectPen,
selectX, selectY, selectWidth, selectHeight);
}
}
private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
Stream sound = Resources.SnapshotSound;
SoundPlayer player = new SoundPlayer(sound);
player.PlaySync();
Application.Exit();
}
}
So what you end up with is basically a rectangle with a negative width and height.
You'll probably want to use the Math.Abs to make sure you get a proper width and height in the cases the difference would become negative.
With that, you will also probably want to draw from different positions based on where your cursor is in relation to the selected point. Something like
selectWidth = e.X - selectX;
selectHeight = e.Y - selectY;
// If width is less than 0, draw from pointer, otherwise from select X
var drawFromX = selectWidth < 0 ? e.X : selectX;
// If height is less than 0, draw from pointer, otherwise from select Y
var drawFromY = selectHeight < 0 ? e.Y : selectY;
pictureBox1.Refresh();
pictureBox1.CreateGraphics().DrawRectangle(selectPen,
drawFromX,
drawFromY,
Math.Abs(selectWidth), // Make sure the rectangle width is positive
Math.Abs(selectHeight)); // Make sure the rectangle height is positive
I have a panel with multiple picturebox created at runtime.
The user will create a rectangle on any picture box and the selected part will be displayed on a preview picturebox.
I have successfully done the above using the below code.
Question
I want to clear the selection rectangle at mouseup event. Used invalidate but not working.
From how to clear the graphics(rectangle shape) in picturebox
Also, when I scroll the panel the same rectangle(mouse selection) is shown on all picturebox.
private void Picture_Paint(object sender, PaintEventArgs e)
{
if (Rect!=null && Rect.Width>0 && Rect.Height>0)
{
e.Graphics.FillRectangle(selectionBrush, Rect);
}
}
private void Picture_MouseDown(object sender, MouseEventArgs e)
{
RecStartpoint = e.Location;
((PictureBox)sender).Invalidate();
}
private void Picture_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left) return;
Point tempEndPoint = e.Location;
Rect.Location = new Point(
Math.Min(RecStartpoint.X, tempEndPoint.X),
Math.Min(RecStartpoint.Y, tempEndPoint.Y));
Rect.Size = new Size(
Math.Abs(RecStartpoint.X - tempEndPoint.X),
Math.Abs(RecStartpoint.Y - tempEndPoint.Y));
((PictureBox)sender).Invalidate();
}
private void Picture_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left) return;
PictureBox org_pic = (PictureBox)(sender);
Point RecEndpoint=e.Location;
int xDown = Math.Min(RecStartpoint.X,RecEndpoint.X);
int yDown = Math.Min(RecStartpoint.Y, RecEndpoint.Y);
int xUp = Math.Max(RecStartpoint.X,RecEndpoint.X);
int yUp = Math.Max(RecStartpoint.Y,RecEndpoint.Y);
Rectangle rec = new Rectangle(xDown, yDown, Math.Abs(xUp - xDown), Math.Abs(yUp - yDown));
xDown = xDown * org_pic.Image.Width / org_pic.Width;
yDown = yDown * org_pic.Image.Height / org_pic.Height;
xUp = xUp * org_pic.Image.Width / org_pic.Width;
yUp = yUp * org_pic.Image.Height / org_pic.Height;
rectCropArea = new Rectangle(xDown, yDown, Math.Abs(xUp - xDown), Math.Abs(yUp - yDown));
pictureBox_preview_photo.Refresh();
Bitmap sourceBitmap = new Bitmap(org_pic.ImageLocation);
Graphics g = pictureBox_preview_photo.CreateGraphics();
g.DrawImage(sourceBitmap, new Rectangle(0, 0, pictureBox_preview_photo.Width, pictureBox_preview_photo.Height), rectCropArea, GraphicsUnit.Pixel);
}
I would try this approach:
First, make Image variable, in the scope of form
public partial class Form1 : Form
{
//variable for holding original image, before rectangle is drawn on it
Image originalImage = null;
public Form1()
{
InitializeComponent();
}
//rest of form's code...
second, save current picture in that variable on MouseDown
private void Picture_MouseDown(object sender, MouseEventArgs e)
{
//save it
startImage = ((PictureBox)sender).Image;
RecStartpoint = e.Location;
((PictureBox)sender).Invalidate();
}
lastly, on the end of MouseUp event, set Rectangle's width and height to zero and restore saved, original image
//snipped code
pictureBox_preview_photo.Refresh();
Bitmap sourceBitmap = new Bitmap(org_pic.ImageLocation);
Graphics g = pictureBox_preview_photo.CreateGraphics();
g.DrawImage(sourceBitmap, new Rectangle(0, 0, pictureBox_preview_photo.Width, pictureBox_preview_photo.Height), rectCropArea, GraphicsUnit.Pixel);
//make rectangle's widht and height 0 so that Paint event won't draw it
Rect.Width = Rect.Height = 0;
//restore image
this.Picture.Image = startImage;
I didn't understand that second question.
I am working on a project where I should draw a line between two picture box.
Please note that I know how to get the coordinates of each picture box, but I don't know what method to use to draw a line between them.
Here is my code so far:
Point p1, p2;
public Form1()
{
InitializeComponent();
int x = pictureBox1.Location.X;
MessageBox.Show(x.ToString());
p1.X = pictureBox1.Location.X;
p1.Y = pictureBox1.Location.Y;
p2.X = pictureBox2.Location.X;
p2.Y = pictureBox2.Location.Y;
}
Appreciate any help!
Hope this will help you
pointArray[iNumberofClicks].X = e.X;
pointArray[iNumberofClicks].Y = e.Y;
Pen PenSpikes = new Pen(Color.Green);
SolidBrush solidBrush = new SolidBrush(Color.Blue);
iNumberofClicks++;
if (iNumberofClicks > 1)
{
Point[] CurrentpointArray = new Point[iNumberofClicks];
for (int i = 0; i < iNumberofClicks; i++)
{
CurrentpointArray[i].X = pointArray[i].X;
CurrentpointArray[i].Y = pointArray[i].Y;
}
Bitmap canvas = new Bitmap(pictureBox1.Width, pictureBox1.Height);
Graphics offScreenDC = Graphics.FromImage(canvas);
// New line!
offScreenDC.DrawImage(pictureBox1.Image, new Point());
offScreenDC.DrawLines(PenSpikes, CurrentpointArray);
pictureBox1.Image = canvas;
offScreenDC.Dispose();
}
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
points.Add(e.Location);
pictureBox1.Invalidate();
}
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{enter code here
if (points.Count > 1)
e.Graphics.DrawLines(Pens.Black, points.ToArray());
}
Lately I found out how to rotate images and I already have problem.
Here is piece of code that I have problem with but you probably don't need to look at it anyway...
Graphics g = e.Graphics;
g.TranslateTransform((float)Width / 2, (float)Height / 2);
g.RotateTransform(myAngle);
Brush brush = new SolidBrush(Color.FromArgb(32, 1, 1, 1));
Pen pen = new Pen(Color.FromArgb(255, 128 , 128, 128), 3);
g.FillRectangle(brush, nX, nY, snX - nX, snY - nY);
g.DrawRectangle(pen, nX, nY, snX - nX, snY - nY);
variables X , sX , snX, Y , sY, snY are coordinates of mouse in specific moments and are calculated mostly in Form1_MouseMove and I can't show what's in there.
How can I make these variables also change no matter what myAngle is?
I've made an example, how to draw on a rotated bitmap: (using a picturebox/bitmap/trackbar)
public partial class Form1 : Form
{
private Bitmap _bitmap;
public Form1()
{
InitializeComponent();
_bitmap = new Bitmap(pictureBox1.Width, pictureBox1.Height);
}
private void TransformGraphics(Graphics g)
{
g.ResetTransform();
g.TranslateTransform(_bitmap.Width / 2, _bitmap.Height / 2);
g.RotateTransform(trackBar1.Value);
g.TranslateTransform(-_bitmap.Width / 2, -_bitmap.Height / 2);
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
TransformGraphics(e.Graphics);
e.Graphics.DrawImage(_bitmap, new Point());
}
private Point? _previousPoint;
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
_previousPoint = e.Location;
}
private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
_previousPoint = null;
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (_previousPoint.HasValue)
{
using (Graphics g = Graphics.FromImage(_bitmap))
{
TransformGraphics(g);
var matrix = g.Transform;
matrix.Invert();
var points = new[] { _previousPoint.Value, e.Location };
matrix.TransformPoints(points);
g.ResetTransform();
g.DrawLine(Pens.Black, points[0], points[1]);
pictureBox1.Invalidate();
_previousPoint = e.Location;
}
}
}
private void trackBar1_ValueChanged(object sender, EventArgs e)
{
pictureBox1.Invalidate();
}
}
You can add another trackbar for scaling.