Related
I created a slider bar user control but at run time when I move the slider to the left or right why it's not getting to the end or swallow?
In the user control designer I added a pictureBox control :
Then in the code I did :
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Extract
{
public partial class Slider : UserControl
{
public float Height;
public float Min = 0.0f;
public float Max = 1.0f;
private float defaultValue = 0.1f;
public Slider()
{
InitializeComponent();
}
private void sliderControl_Paint(object sender, PaintEventArgs e)
{
float bar_size = 0.45f;
float x = Bar(defaultValue);
int y = (int)(sliderControl.Height * bar_size);
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
e.Graphics.FillRectangle(Brushes.DimGray, 0, y, sliderControl.Width, y / 2);
e.Graphics.FillRectangle(Brushes.Red, 0, y, x, sliderControl.Height - 2 * y);
using (Pen pen = new Pen(Color.Black, 8))
{
e.Graphics.FillRectangle(Brushes.Red, 0, y, x, y / 2);
FillCircle(e.Graphics, Brushes.Red, x, y + y / 4, y / 2);
}
using (Pen pen = new Pen(Color.White, 5))
{
DrawCircle(e.Graphics, pen, x, y + y / 4, y/ 2);
}
}
public static void DrawCircle(Graphics g, Pen pen,
float centerX, float centerY, float radius)
{
g.DrawEllipse(pen, centerX - radius, centerY - radius,
radius + radius, radius + radius);
}
public static void FillCircle(Graphics g, Brush brush,
float centerX, float centerY, float radius)
{
g.FillEllipse(brush, centerX - radius, centerY - radius,
radius + radius, radius + radius);
}
private float Bar(float value)
{
return (sliderControl.Width - 24) * (value - Min) / (float)(Max - Min);
}
private void Thumb(float value)
{
if (value < Min) value = Min;
if (value > Max) value = Max;
defaultValue = value;
sliderControl.Refresh();
}
private float SliderWidth(int x)
{
return Min + (Max - Min) * x / (float)(sliderControl.Width);
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
MaintainPictureBoxSize();
}
private void MaintainPictureBoxSize()
{
sliderControl.SizeMode = PictureBoxSizeMode.Normal;
sliderControl.Location = new Point();
sliderControl.Size = new Size();
var clientSize = this.ClientSize;
if (sliderControl.Image == null)
sliderControl.Size = clientSize;
else
{
Size s = sliderControl.Image.Size;
sliderControl.Size = new Size(
clientSize.Width > s.Width ? clientSize.Width : s.Width,
clientSize.Height > s.Height ? clientSize.Height : s.Height);
}
}
bool mouse = false;
private void sliderControl_MouseDown(object sender, MouseEventArgs e)
{
mouse = true;
Thumb(SliderWidth(e.X));
}
private void sliderControl_MouseMove(object sender, MouseEventArgs e)
{
if (!mouse) return;
Thumb(SliderWidth(e.X));
}
private void sliderControl_MouseUp(object sender, MouseEventArgs e)
{
mouse = false;
}
}
}
When I drag the control to the form1 designer and then running the application then when I drag the slider for example to the left or to the right the circle of the slider is partly swallow.
and if I resize the control in form1 designer to be smaller and then running the application to left it swallow as before but to the right it's not getting to the end at all.
The easiest way to explain it is to show an image:
Now, inside the picture box, imagine the thumb circle to be in the left most and right most positions. This means that the bar must start at x = radius and that the width of the bar must be the width of the picture box minus twice the radius.
Everything must be drawn inside the picture box (dotted line). But this needs not to be in a PictureBox placed on a UserControl. Let's derive the slider from Control instead.
public class Slider : Control
{
...
}
Now, after having compiled this code for the first time, this Slider automatically appears in the Toolbox window and is ready to be placed on a form in the forms designer.
Since we want to be able to set its properties in the properties window and we want to be able to read the current value after sliding, let's add an event and some properties.
public event EventHandler ValueChanged;
private float _min = 0.0f;
public float Min
{
get => _min;
set {
_min = value;
RecalculateParameters();
}
}
private float _max = 1.0f;
public float Max
{
get => _max;
set {
_max = value;
RecalculateParameters();
}
}
private float _value = 0.3f;
public float Value
{
get => _value;
set {
_value = value;
ValueChanged?.Invoke(this, EventArgs.Empty);
RecalculateParameters();
}
}
This requires some fields and the RecalculateParameters method.
private float _radius;
private PointF _thumbPos;
private SizeF _barSize;
private PointF _barPos;
private void RecalculateParameters()
{
_radius = 0.5f * ClientSize.Height;
_barSize = new SizeF(ClientSize.Width - 2f * _radius, 0.5f * ClientSize.Height);
_barPos = new PointF(_radius, (ClientSize.Height - _barSize.Height) / 2);
_thumbPos = new PointF(
_barSize.Width / (Max - Min) * Value + _barPos.X,
_barPos.Y + 0.5f * _barSize.Height);
Invalidate();
}
Inside this derived control we override the event handlers (On... methods) instead of subscribing to the events:
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.FillRectangle(Brushes.DimGray,
_barPos.X, _barPos.Y, _barSize.Width, _barSize.Height);
e.Graphics.FillRectangle(Brushes.Red,
_barPos.X, _barPos.Y, _thumbPos.X - _barPos.X, _barSize.Height);
e.Graphics.FillCircle(Brushes.White, _thumbPos.X, _thumbPos.Y, _radius);
e.Graphics.FillCircle(Brushes.Red, _thumbPos.X, _thumbPos.Y, 0.7f * _radius);
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
RecalculateParameters();
}
Now let's compile this code and let's add a slider to a form. See how we can resize it in the designer.
Note also, that in the properties window we see the new Slider properties Max, Min and Value in the "Misc" section. We can change them here and the thumb position is automatically updated.
We still need the code to enable moving the slider. When we click on the thumb, we might have clicked a bit off its center. It feels natural to keep this offset while moving the mouse. Therefore, we store this difference in a variable _delta.
bool _moving = false;
SizeF _delta;
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
// Difference between tumb and mouse position.
_delta = new SizeF(e.Location.X - _thumbPos.X, e.Location.Y - _thumbPos.Y);
if (_delta.Width * _delta.Width + _delta.Height * _delta.Height <= _radius * _radius) {
// Clicking inside thumb.
_moving = true;
}
}
We also calculate the distance of the mouse position to the thumb position in OnMouseDown by using the Pythagorean theorem. Only if the mouse is inside the thumb, we initiate moving the thumb by setting _moving = true;
In OnMouseMove we calculate and set the new Value. This automatically triggers recalculating the parameters and redraws the slider.
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (_moving) {
float thumbX = e.Location.X - _delta.Width;
if (thumbX < _barPos.X) {
thumbX = _barPos.X;
} else if (thumbX > _barPos.X + _barSize.Width) {
thumbX = _barPos.X + _barSize.Width;
}
Value = (thumbX - _barPos.X) * (Max - Min) / _barSize.Width;
}
}
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
_moving = false;
}
We can test the slider by adding a TextBox to the form and responding to the ValueChanged event. We can add an event handler by switching the properties window to "Events" by clicking on the flash symbol and then double click on ValueChanged in the "Misc" section.
private void Slider1_ValueChanged(object sender, EventArgs e)
{
textBox1.Text = slider1.Value.ToString();
}
Now, when we move the thumb, the text box displays the values.
Here again the whole code of the slider (using C# 10.0 file scoped namespaces):
using System.Drawing.Drawing2D;
namespace WinFormsSliderBar;
public class Slider : Control
{
private float _radius;
private PointF _thumbPos;
private SizeF _barSize;
private PointF _barPos;
public event EventHandler ValueChanged;
public Slider()
{
// This reduces flicker
DoubleBuffered = true;
}
private float _min = 0.0f;
public float Min
{
get => _min;
set {
_min = value;
RecalculateParameters();
}
}
private float _max = 1.0f;
public float Max
{
get => _max;
set {
_max = value;
RecalculateParameters();
}
}
private float _value = 0.3f;
public float Value
{
get => _value;
set {
_value = value;
ValueChanged?.Invoke(this, EventArgs.Empty);
RecalculateParameters();
}
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.FillRectangle(Brushes.DimGray,
_barPos.X, _barPos.Y, _barSize.Width, _barSize.Height);
e.Graphics.FillRectangle(Brushes.Red,
_barPos.X, _barPos.Y, _thumbPos.X - _barPos.X, _barSize.Height);
e.Graphics.FillCircle(Brushes.White, _thumbPos.X, _thumbPos.Y, _radius);
e.Graphics.FillCircle(Brushes.Red, _thumbPos.X, _thumbPos.Y, 0.7f * _radius);
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
RecalculateParameters();
}
private void RecalculateParameters()
{
_radius = 0.5f * ClientSize.Height;
_barSize = new SizeF(ClientSize.Width - 2f * _radius, 0.5f * ClientSize.Height);
_barPos = new PointF(_radius, (ClientSize.Height - _barSize.Height) / 2);
_thumbPos = new PointF(
_barSize.Width / (Max - Min) * Value + _barPos.X,
_barPos.Y + 0.5f * _barSize.Height);
Invalidate();
}
bool _moving = false;
SizeF _delta;
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
// Difference between tumb and mouse position.
_delta = new SizeF(e.Location.X - _thumbPos.X, e.Location.Y - _thumbPos.Y);
if (_delta.Width * _delta.Width + _delta.Height * _delta.Height <= _radius * _radius) {
// Clicking inside thumb.
_moving = true;
}
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (_moving) {
float thumbX = e.Location.X - _delta.Width;
if (thumbX < _barPos.X) {
thumbX = _barPos.X;
} else if (thumbX > _barPos.X + _barSize.Width) {
thumbX = _barPos.X + _barSize.Width;
}
Value = (thumbX - _barPos.X) * (Max - Min) / _barSize.Width;
}
}
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
_moving = false;
}
}
and the graphic extensions for drawing circles:
namespace WinFormsSliderBar;
public static class GraphicsExtensions
{
public static void DrawCircle(this Graphics g, Pen pen,
float centerX, float centerY, float radius)
{
g.DrawEllipse(pen, centerX - radius, centerY - radius,
radius + radius, radius + radius);
}
public static void FillCircle(this Graphics g, Brush brush,
float centerX, float centerY, float radius)
{
g.FillEllipse(brush, centerX - radius, centerY - radius,
radius + radius, radius + radius);
}
}
Issue: Attempting to zoom (scale) an Image from (or at the) mouse location using transforms in the Paint event to translate bitmap origin to mouse location, then scale the Image and translate its origin back.
The Image jumps and fails to scale from the relocated origin when translating the mouse location.
Rotate, scale, and pan function correctly without translating to the the mouse location.
Running on .Net 4.7.2, using Visual Studio in Windows 10 1909
v18363.778
The relevant code blocks:
private void trackBar1_Scroll(object sender, EventArgs e)
{
// Get rotation angle
ang = trackBar1.Value;
pnl1.Invalidate();
}
private void pnl1_MouseWheel(object sender, MouseEventArgs e)
{
// Get mouse location
mouse = e.location;
// Get new scale (zoom) factor
zoom = (float)(e.Delta > 0 ? zoom * 1.05 : zoom / 1.05);
pnl1.Invalidate();
}
private void pnl1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left) return;
pan = true;
mouX = e.X;
mouY = e.Y;
oldX = imgX;
oldY = imgY;
}
private void pnl1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left || !pan) return;
// Coordinates of panned image
imgX = oldX + e.X - mouX;
imgY = oldY + e.Y - mouY;
pnl1.Invalidate();
}
private void pnl1_MouseUp(object sender, MouseEventArgs e)
{
pan = false;
}
private void pnl1_Paint(object sender, PaintEventArgs e)
{
// Apply rotation angle # center of bitmap
e.Graphics.TranslateTransform(img.Width / 2, img.Height / 2);
e.Graphics.RotateTransform(ang);
e.Graphics.TranslateTransform(-img.Width / 2, -img.Height / 2);
// Apply scaling factor - focused # mouse location
e.Graphics.TranslateTransform(mouse.X, mouse.Y, MatrixOrder.Append);
e.Graphics.ScaleTransform(zoom, zoom, MatrixOrder.Append);
e.Graphics.TranslateTransform(-mouse.X, -mouse.Y, MatrixOrder.Append);
// Apply drag (pan) location
e.Graphics.TranslateTransform(imgX, imgY, MatrixOrder.Append);
// Draw "bmp" # location
e.Graphics.DrawImage(img, 0, 0);
}
A few suggestions and a couple of tricks.
Not exactly tricks, just some methods to speed up the calculations when more than one graphic transformation is in place.
Divide and conquer: split the different graphics effects and transformations in different, specialized, methods that do one thing. Then design in a way that makes it possible for these methods to work together when needed.
Keep it simple: when Graphics objects need to accumulate more than a couple of transformations, the order in which Matrices are stacked can cause misunderstandings. It's simpler (and less prone to generate weird outcomes) to calculate some generic transformations (translate and scale, mostly) beforehand, then let GDI+ render already pre-cooked objects and shapes.
Here, only Matrix.RotateAt and Matrix.Multiply are used.
Some notes about Matrix transformations here: Flip the GraphicsPath
Use the right tools: for example, a Panel used as canvas is not exactly the best choice. This Control is not double-buffered; this feature can be enabled, but the Panel class is not meant for drawing, while a PictureBox (or a non-System flat Label) supports it on its own.
Some more notes here: How to apply a fade transition effect to Images
The sample code shows 4 zoom methods, plus generates rotation transformations (which work side-by-side, don't accumulate).
The Zoom modes are selected using an enumerator (private enum ZoomMode):
Zoom modes:
ImageLocation: Image scaling is performed in-place, keeping the current Location on the canvas in a fixed position.
CenterCanvas: while the Image is scaled, it remains centered on the Canvas.
CenterMouse: the Image is scaled and translated to center itself on the current Mouse location on the Canvas.
MouseOffset: the Image is scaled and translated to maintain a relative position determined by the initial location of the Mouse pointer on the Image itself.
You can notice that the code simplifies all the calculations, applying translations exclusively relative to the Rectangle that defines the current Image bounds and only in relation to the Location of this shape.
The Rectangle is only scaled when the calculation needs to preemptively determine what the Image size will be after the Mouse Wheel has generated the next Zoom factor.
Visual sample of the implemented functionalities:
Sample code:
canvas is the Custom Control, derived from PictureBox (you can find its definition at the bottom). This control is added to the Form in code, here. Modify as needed.
trkRotationAngle is the TrackBar used to define the current rotation of the Image. Add this control to the Form in the designer.
radZoom_CheckedChanged is the event handler of all the RadioButtons used to set the current Zoom Mode. The value these Controls set is assigned in their Tag property. Add these controls to the Form in the designer.
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
using System.Windows.Forms;
public partial class frmZoomPaint : Form
{
private float rotationAngle = 0.0f;
private float zoomFactor = 1.0f;
private float zoomStep = .05f;
private RectangleF imageRect = RectangleF.Empty;
private PointF imageLocation = PointF.Empty;
private PointF mouseLocation = PointF.Empty;
private Bitmap drawingImage = null;
private PictureBoxEx canvas = null;
private ZoomMode zoomMode = ZoomMode.ImageLocation;
private enum ZoomMode
{
ImageLocation,
CenterCanvas,
CenterMouse,
MouseOffset
}
public frmZoomPaint()
{
InitializeComponent();
string imagePath = [Path of the Image];
drawingImage = (Bitmap)Image.FromStream(new MemoryStream(File.ReadAllBytes(imagePath)));
imageRect = new RectangleF(Point.Empty, drawingImage.Size);
canvas = new PictureBoxEx(new Size(555, 300));
canvas.Location = new Point(10, 10);
canvas.MouseWheel += canvas_MouseWheel;
canvas.MouseMove += canvas_MouseMove;
canvas.MouseDown += canvas_MouseDown;
canvas.MouseUp += canvas_MouseUp;
canvas.Paint += canvas_Paint;
Controls.Add(canvas);
}
private void canvas_MouseWheel(object sender, MouseEventArgs e)
{
mouseLocation = e.Location;
float zoomCurrent = zoomFactor;
zoomFactor += e.Delta > 0 ? zoomStep : -zoomStep;
if (zoomFactor < .10f) zoomStep = .01f;
if (zoomFactor >= .10f) zoomStep = .05f;
if (zoomFactor < .0f) zoomFactor = zoomStep;
switch (zoomMode) {
case ZoomMode.CenterCanvas:
imageRect = CenterScaledRectangleOnCanvas(imageRect, canvas.ClientRectangle);
break;
case ZoomMode.CenterMouse:
imageRect = CenterScaledRectangleOnMousePosition(imageRect, e.Location);
break;
case ZoomMode.MouseOffset:
imageRect = OffsetScaledRectangleOnMousePosition(imageRect, zoomCurrent, e.Location);
break;
default:
break;
}
canvas.Invalidate();
}
private void canvas_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left) return;
mouseLocation = e.Location;
imageLocation = imageRect.Location;
canvas.Cursor = Cursors.NoMove2D;
}
private void canvas_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left) return;
imageRect.Location =
new PointF(imageLocation.X + (e.Location.X - mouseLocation.X),
imageLocation.Y + (e.Location.Y - mouseLocation.Y));
canvas.Invalidate();
}
private void canvas_MouseUp(object sender, MouseEventArgs e) =>
canvas.Cursor = Cursors.Default;
private void canvas_Paint(object sender, PaintEventArgs e)
{
var drawingRect = GetDrawingImageRect(imageRect);
using (var mxRotation = new Matrix())
using (var mxTransform = new Matrix()) {
e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
e.Graphics.PixelOffsetMode = PixelOffsetMode.Half;
mxRotation.RotateAt(rotationAngle, GetDrawingImageCenterPoint(drawingRect));
mxTransform.Multiply(mxRotation);
e.Graphics.Transform = mxTransform;
e.Graphics.DrawImage(drawingImage, drawingRect);
}
}
private void trkRotationAngle_ValueChanged(object sender, EventArgs e)
{
rotationAngle = trkAngle.Value;
canvas.Invalidate();
canvas.Focus();
}
private void radZoom_CheckedChanged(object sender, EventArgs e)
{
var rad = sender as RadioButton;
if (rad.Checked) {
zoomMode = (ZoomMode)int.Parse(rad.Tag.ToString());
}
canvas.Focus();
}
#region Drawing Methods
public RectangleF GetScaledRect(RectangleF rect, float scaleFactor) =>
new RectangleF(rect.Location,
new SizeF(rect.Width * scaleFactor, rect.Height * scaleFactor));
public RectangleF GetDrawingImageRect(RectangleF rect) =>
GetScaledRect(rect, zoomFactor);
public PointF GetDrawingImageCenterPoint(RectangleF rect) =>
new PointF(rect.X + rect.Width / 2, rect.Y + rect.Height / 2);
public RectangleF CenterScaledRectangleOnCanvas(RectangleF rect, RectangleF canvas)
{
var scaled = GetScaledRect(rect, zoomFactor);
rect.Location = new PointF((canvas.Width - scaled.Width) / 2,
(canvas.Height - scaled.Height) / 2);
return rect;
}
public RectangleF CenterScaledRectangleOnMousePosition(RectangleF rect, PointF mousePosition)
{
var scaled = GetScaledRect(rect, zoomFactor);
rect.Location = new PointF(mousePosition.X - (scaled.Width / 2),
mousePosition.Y - (scaled.Height / 2));
return rect;
}
public RectangleF OffsetScaledRectangleOnMousePosition(RectangleF rect, float currentZoom, PointF mousePosition)
{
var currentRect = GetScaledRect(imageRect, currentZoom);
if (!currentRect.Contains(mousePosition)) return rect;
float scaleRatio = currentRect.Width / GetScaledRect(rect, zoomFactor).Width;
PointF mouseOffset = new PointF(mousePosition.X - rect.X, mousePosition.Y - rect.Y);
PointF scaledOffset = new PointF(mouseOffset.X / scaleRatio, mouseOffset.Y / scaleRatio);
PointF position = new PointF(rect.X - (scaledOffset.X - mouseOffset.X),
rect.Y - (scaledOffset.Y - mouseOffset.Y));
rect.Location = position;
return rect;
}
#endregion
}
The simple PictureBoxEx custom control (modify and extend as needed):
This PictureBox is selectable, so it can be focused, with a Mouse click
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
[DesignerCategory("Code")]
public class PictureBoxEx : PictureBox
{
public PictureBoxEx() : this (new Size(200, 200)){ }
public PictureBoxEx(Size size) {
SetStyle(ControlStyles.Selectable | ControlStyles.UserMouse, true);
BorderStyle = BorderStyle.FixedSingle;
Size = size;
}
}
#Jimi: Thank you for the detailed information - very useful for visualizing the concepts involved in the graphics manipulations. I had arrived at a functioning solution (see code below) however, your code utilizes steps with greater efficiency. Admittedly, my code is developed with more of an intent to learn the mechanics of image manipulation - as I am still at the early part of the learning curve. Nonetheless, your illustration of the mechanics and techniques is extremely helpful.
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace ZoomImage
{
public partial class Form1 : Form
{
Image img;
Bitmap bmp;
float ang = 0;
float zoom = 1;
bool pan;
bool? ctr = false;
Point mcurrent;
PointF mouse;
PointF image;
PointF _image;
PointF rotate;
public Form1()
{
InitializeComponent();
MouseWheel += mouseWheel;
img = Image.FromFile(#"C:\testimage.jpg");
bmp = new Bitmap(img);
// Set initial scale to fit canvas window
float wRatio = (float)pbx.Width / (float)img.Width;
float hRatio = (float)pbx.Height / (float)img.Height;
zoom = Math.Min(wRatio, hRatio);
image.X = (pbx.Width - zoom * img.Width) / 2;
image.Y = (pbx.Height - zoom * img.Height) / 2;
}
private void label()
{
string _imgX = string.Format("{0:000}", image.X);
string _imgY = string.Format("{0:000}", image.Y);
lbl1.Text = "Location: " + _imgX + ", " + _imgY + "\r\nRotation: " + ang + "\r\nZoom: " + zoom + "\r\nMouse: " + mcurrent.X + ", " + mcurrent.Y;
}
private void btnRotate_Click(object sender, EventArgs e)
{
if (ModifierKeys == Keys.Control)
{
string msg = "Set center of rotation point:\r\n\nMove mouse to desired center ";
msg += "of rotation then hold \"Alt\" and left-click.\r\n\n";
msg += "To restore center of rotation to center of image:\r\n\nHold \"Shift\" and";
msg += " click \"Rotate\".";
MessageBox.Show(msg,"Change center of rotation");
ctr = null;
pbx.Focus();
return;
}
else if (ModifierKeys == Keys.Shift)
{
ctr = false;
return;
}
ang = ang == 270 ? 0 : ang += 90;
if (ang > 360) ang -= 360;
trackBar1.Value = (int)ang;
ctr = ctr == null ? false : ctr;
if (ctr == false) rotate = new PointF(img.Width / 2, img.Height / 2);
pbx.Invalidate();
}
private void trackBar1_Scroll(object sender, EventArgs e)
{
ang = trackBar1.Value;
if (ctr == false) rotate = new PointF(img.Width / 2, img.Height / 2);
pbx.Invalidate();
}
private void mouseWheel(object sender, MouseEventArgs e)
{
mouse = new PointF(e.X - image.X, e.Y - image.Y);
float zinc = 0.05f;
float zfac = 1 + zinc;
zoom = (float)(e.Delta > 0 ? zoom * (zfac) : zoom / (zfac));
// Adjust "img" (bitmap) orgin to maintain fixed focus # mouse location
if (e.Delta > 0)
{
image.X -= zinc * mouse.X;
image.Y -= zinc * mouse.Y;
}
else
{
image.X += (1 - 1 / (zfac)) * mouse.X;
image.Y += (1 - 1 / (zfac)) * mouse.Y;
}
image = new PointF(image.X, image.Y);
pbx.Invalidate();
}
private void mouseDown(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left) return;
if (ModifierKeys == Keys.Alt && ctr == null)
{
ctr = true;
rotate = new PointF((e.X - image.X) / zoom, (e.Y - image.Y) / zoom);
return;
}
pan = true;
mouse = e.Location;
_image = image;
}
private void mouseMove(object sender, MouseEventArgs e)
{
mcurrent = e.Location;
label();
if (e.Button != MouseButtons.Left || !pan) return;
image.X = _image.X + e.X - mouse.X;
image.Y = _image.Y + e.Y - mouse.Y;
image = new PointF(image.X, image.Y);
pbx.Invalidate();
}
private void mouseUp(object sender, MouseEventArgs e)
{
pan = false;
}
private void pbx_Paint(object sender, PaintEventArgs e)
{
label();
// Generate bitmap "bmp" - this can be saved as drawn...if deisred
bmp = new Bitmap(img.Width, img.Height);
using (Graphics g = Graphics.FromImage(bmp))
{
Matrix transform = new Matrix();
transform.Scale(zoom, zoom, MatrixOrder.Append);
transform.RotateAt(ang, rotate);
transform.Translate(image.X, image.Y, MatrixOrder.Append);
g.Transform = transform;
g.DrawImage(img, 0, 0);
}
e.Graphics.DrawImage(bmp, 0, 0);
}
}
}
i have PictureBox, how can I draw a shape/line that fires on MouseEnter event and change color or do more.
private void ImgViewer_Paint(object sender, PaintEventArgs e)
{
var graph = e.Graphics;
using (var pen = new Pen(Color.FromArgb(0, 255, 0)))
graph.DrawLine(pen, x1, y1, x2, y2);
}
this code is not enough, i guess
If you know the equation of the shape you could calculate whether the mouse is within or outside the shape area. Note that this is easy if the shape is consisted of the straight lines or circles (ellipses) for which the geometrical equations are relatively simple. For instance if your shape is a triangle with x and y coordinates (10,10), (50,10) and (30,50) than you should derive the equations of the lines using the equation of the line in two points:
y-y1 = ((y2-y1)/(x2-x1))*(x-x1)
the equations of the lines of our triangle would be:
y=1
y=2*x-10
y=-2*x+110
We should draw that triangle on some canvas, let's say on the PictureBox with FixedSingle border. Add the Paint event handler
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
Point[] p = new Point[3];
p[0] = new Point(10,10);
p[1] = new Point(50,10);
p[2] = new Point(30,50);
e.Graphics.DrawLines(Pens.Black, p);
e.Graphics.FillPolygon(Brushes.Red, p);
}
We should add the MouseMove event handler for the PictureBox
bool inside = false;
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Y > 10 && e.Y < 2 * e.X - 10 && e.Y < -2 * e.X + 110)
{
if (!inside)
{
inside = true;
HandleMouseEnter();
}
}
else
inside = false;
}
void HandleMouseEnter()
{
MessageBox.Show("Mouse inside");
}
In if statement whether the mouse cursor is within the triangle (note that the coordinate origin in C# is on the top-left corner but it is similar to the real geometry). The HandleMouseEnter is the method that handles the mouse enter.
You could use similar approach for an arbitrary shape but you should have geometry equations that describe it.
I'm designing an app that loads an image into a PictureBox and allows the user to draw a selection rectangle on it. Currently, I use the Paint event and a boolean to clear the previously-drawn rectangle (since it's a drag-able selection box).
Question:
The code fails because the previous rectangle isn't cleared from the image. Although each rectangle that is drawn is transparent, the effect is an opaque rectangle because the previous rectangles aren't cleared. How can I clear these rectangles?
Logic:
saveState defaults to true. When the Paint event is triggered the first time, the state containing the normal image is saved. When a MouseDown event is triggered, we register the starting position of the rectangle and a boolean that indicates a rectangle is being drawn.
When a MouseMove event is triggered, we draw a rectangle at the current coordinates. Since the Paint event is triggered (I think) when this is drawn and saveState is false, we restore the normal image before drawing the rectangle.
Finally, when a MouseUp event is triggered, saveState is set to true, so the graphics state, with the last rectangle drawn, is saved, and we're back at the beginning.
I read about ControlPaint.DrawReversibleFrame, but since this article and this question give me the impression that it isn't designed for drawing on images, but rather on the screen or the form directly, I'm not sure that's what I need.
Code:
public partial class MainWindow : Form
{
private bool drawingRectangle;
private int x1, y1, x2, y2;
private Image currentImage;
private GraphicsState previousState;
private bool saveState;
public MainWindow()
{
InitializeComponent();
this.drawingRectangle = false;
this.saveState = true;
}
private void EditorPictureBox_MouseDown(object sender, MouseEventArgs e)
{
this.x1 = e.X;
this.y1 = e.Y;
this.drawingRectangle = true;
}
private void EditorPictureBox_MouseMove(object sender, MouseEventArgs e)
{
if (this.drawingRectangle)
{
this.x2 = e.X;
this.y2 = e.Y;
Graphics g = Graphics.FromImage(this.currentImage);
int[] dim = ImageLibrary.CalculateRectangleDimensions(this.x1, this.y1, this.x2, this.y2);
g.FillRectangle(new SolidBrush(Color.FromArgb(100, 128, 255, 255)), dim[0], dim[1], dim[2], dim[3]);
this.Refresh();
}
}
private void EditorPictureBox_Paint(object sender, PaintEventArgs e)
{
if (this.saveState)
{
this.previousState = e.Graphics.Save();
this.saveState = false;
}
else
e.Graphics.Restore(this.previousState);
}
private void EditorPictureBox_MouseUp(object sender, MouseEventArgs e)
{
if (this.drawingRectangle)
{
this.drawingRectangle = false;
// When the mouse click is released, save the graphics state
this.saveState = true;
}
}
private void LoadImage2Button_Click(object sender, EventArgs e)
{
this.currentImage = Image.FromFile("goat2.jpg");
this.EditorPictureBox.Image = this.currentImage;
}
}
This is the code for CalculateRectangleDimensions (stored in a static library):
public static int[] CalculateRectangleDimensions(int x1, int y1, int x2, int y2)
{
int[] dimensions = new int[4]; // x1, y1, width, height
if (x1 <= x2) // Mouse was dragged to the right
{
dimensions[0] = x1;
dimensions[2] = x2 - x1;
}
else // Mouse was dragged to the right
{
dimensions[0] = x2;
dimensions[2] = x1 - x2;
}
if (y1 <= y2) // Mouse was dragged up
{
dimensions[1] = y1;
dimensions[3] = y2 - y1;
}
else // Mouse was dragged down
{
dimensions[1] = y2;
dimensions[3] = y1 - y2;
}
return dimensions;
}
Graphics.Save doesn't save the entire contents of the graphics when you call it, it just saves state information, like translations, scale, transforms, etc.
if you want to undo a draw you've already done, you'd have to do something like reversible paint does, or you'll have to redraw the original image when you want to undo your draw.
I have a program in C# (Windows Forms) which has a rectangle on a Picture Box. They can be drawn at an angle too (rotated). I want to rotate that rectangle using my mouse movements.
I have the code for moving that rectangle
Rectangle areaRect = new Rectangle(100,100, 300, 300);
Bool dragging = false;
Point ptOld = new Point(0, 0);
protected override void OnPaint(PaintEventArgs e)
{
Graphics dcPaint = e.Graphics;
dcPaint.DrawRectangle(rectPen, areaRect);
}
protected override void OnMouseDown(MouseEventArgs e)
{
ptOld = new Point(e.X, e.Y);
dragging = true;
}
protected override void OnMouseMove(MouseEventArgs e)
{
if(dragging = true)
{
Point ptNew = new Point(e.X, e.Y);
int dx = ptNew.X - ptOld.X;
int dy = ptNew.Y - ptOld.Y;
areaRect.Offset(dx, dy); // This one moves the rectangle
ptOld = ptNew;
this.Invalidate();
}
}
protected override void OnMouseUp(MouseEventArgs e)
{
dragging = false;
}
Now My requirement is to rotate this rectangle, Any idea, how that can be achieved.
I think you want to calculate angle between two points on X-axis. If so, try the following code:
const double Rad2Deg = 180.0 / Math.PI;
return Math.Atan2(ptOld.Y - e.Y, e.X - ptOld.X) * Rad2Deg;
Also check out this article on calculating angle between two points
When rotating rectangle with mouse, you define the center of rotation (centerXY), in you case it will be the center of the rectangle maybe.
On mouse down record mouse coordinates, mouse_downXY. These two points define a base line. When moving mouse you'll define another line, formed by current mouse coordinates and the rectangle center.
So you'll need to compute the angle between line (centerXY, mouse_downXY) and (centerXY, current_mouseXY). Computing angle between 2 lines with knowing 3 points coordinates is simple trigonometry, so I won't write code for you :) However this post has the answer.
You can calculate the angle using the difference between the old and the new x mouse coordinate (dx in your example). You can use the RotateTransform method of the Graphics object to rotate the rectangle.
I modified your code to do the rotation in addition to the translation. You can move the rectangle with the left mouse button and you can rotate it using the right mouse button.
Rectangle areaRect = new Rectangle(100, 100, 300, 300);
bool dragging = false;
bool rotating = false;
Point ptOld = new Point(0, 0);
float angle = 0;
protected override void OnPaint(PaintEventArgs e)
{
Graphics dcPaint = e.Graphics;
dcPaint.RotateTransform(angle);
dcPaint.DrawRectangle(Pens.Black, areaRect);
dcPaint.RotateTransform(-angle);
}
protected override void OnMouseDown(MouseEventArgs e)
{
ptOld = new Point(e.X, e.Y);
if (e.Button == MouseButtons.Left)
{
dragging = true;
}
if (e.Button == MouseButtons.Right)
{
rotating = true;
}
}
protected override void OnMouseMove(MouseEventArgs e)
{
if (dragging == true)
{
Point ptNew = new Point(e.X, e.Y);
int dx = ptNew.X - ptOld.X;
int dy = ptNew.Y - ptOld.Y;
areaRect.Offset(dx, dy); // This one moves the rectangle
ptOld = ptNew;
this.Invalidate();
}
if (rotating == true)
{
Point ptNew = new Point(e.X, e.Y);
int dx = ptNew.X - ptOld.X;
angle = angle + dx / 10f;
ptOld = ptNew;
this.Invalidate();
}
}
protected override void OnMouseUp(MouseEventArgs e)
{
dragging = false;
rotating = false;
}
Right now, the rectangle is rotated around its top left corner. If you apply a translation before the rotation, you can get it to rotate around its middle.