PictureBox zoom and scroll on mouse wheel C# - c#

I'm trying to create pictureBox with which I could zoom in/out to cursor, like google maps.
Some code:
int viewRectWidth;
int viewRectHeight;
public float zoomshift = 0.05f;
int xForScroll;
int yForScroll;
float zoom = 1.0f;
public float Zoom
{
get { return zoom; }
set
{
if (value < 0.001f) value = 0.001f;
zoom = value;
displayScrollbar();
setScrollbarValues();
Invalidate();
}
}
Size canvasSize = new Size(60, 40);
public Size CanvasSize
{
get { return canvasSize; }
set
{
canvasSize = value;
displayScrollbar();
setScrollbarValues();
Invalidate();
}
}
Bitmap image;
public Bitmap Image
{
get { return image; }
set
{
image = value;
displayScrollbar();
setScrollbarValues();
Invalidate();
}
}
InterpolationMode interMode = InterpolationMode.HighQualityBilinear;
public InterpolationMode InterpolationMode
{
get { return interMode; }
set { interMode = value; }
}
public ZoomablePictureBox()
{
InitializeComponent();
this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint |
ControlStyles.ResizeRedraw | ControlStyles.UserPaint |
ControlStyles.DoubleBuffer, true);
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
if(image != null)
{
Rectangle srcRect, distRect;
Point pt = new Point((int)(hScrollBar1.Value / zoom), (int)(vScrollBar1.Value / zoom));
if (canvasSize.Width * zoom < viewRectWidth)
srcRect = new Rectangle(0, 0, canvasSize.Width, canvasSize.Height);
else srcRect = new Rectangle(pt, new Size((int)(viewRectWidth / zoom), (int)(viewRectHeight / zoom)));
distRect = new Rectangle((int)(-srcRect.Width / 2), -srcRect.Height / 2, srcRect.Width, srcRect.Height);
Matrix mx = new Matrix();
mx.Scale(zoom, zoom);
mx.Translate(viewRectWidth / 2.0f, viewRectHeight / 2.0f, MatrixOrder.Append);
Graphics g = e.Graphics;
g.InterpolationMode = interMode;
g.Transform = mx;
g.DrawImage(image, distRect, srcRect, GraphicsUnit.Pixel);
}
}
Now I need mouse wheel event to zoom and scroll to mouse point, and I just can't figure out formula to what values I should set scrollBars.
private void onMouseWheel(object sender, MouseEventArgs e)
{
if (ModifierKeys == Keys.Control)
{
this.Zoom += e.Delta / 120 * this.zoomshift;
vScrollBar1.Value = ?;
hScrollBar1.Value = ?;
}
}
any help would be appreciated.
Regards, Tomas

If you want to keep the scrollbars at the same relative position, I think the following should work:
float oldvMax = vScrollBar1.Maximum;
int oldvValue = vScrollBar1.Value;
float oldhMax = hScrollBar1.Maximum;
int oldhValue = hScrollBar1.Value;
this.Zoom += e.Delta / 120 * this.zoomshift;
vScrollBar1.Value = (int)((oldvValue / oldvMax) * vScrollBar1.Maximum);
hScrollBar1.Value = (int)((oldhValue / oldhMax) * hScrollBar1.Maximum);

Related

Custom Control not painting?

I have created a custom control for drawing a Dial type gauge but I've come across a strange issue. The form that I am using the gauge on has two of these gauges. One gauge works perfectly fine (the one docked on the left), but the one on the right literally does not paint, even in Visual Studio.
If I'm in Visual Studio and I change from a code tab to the tab with the gauge, that one gauge is never drawn and it still shows the text from the previous tab. If I click on the gauge, then the gauge suddenly draws.
This happens when the program is run as well but instead of not redrawing the space, it is just completely transparent, even erasing the form background.
The OnPaint Code is like this:
protected override void OnPaint(PaintEventArgs e)
{
int size = ClientSize.Height - GaugeThickness;
float x = (GaugeThickness / 2) + OffsetX;
float y = (GaugeThickness / 2) + OffsetY;
if (this.HorizontalAlignment == HorizontalAlignment.Left)
{
x = ((size / 2) * -1) + OffsetX;
}
else if (this.HorizontalAlignment == HorizontalAlignment.Right)
{
x = (size / 2) + OffsetX;
}
if (this.VerticalAlignment == VerticalAlignment.Top)
{
y = ((size / 2) * -1) + OffsetY;
}
else if (this.VerticalAlignment == VerticalAlignment.Bottom)
{
y = (size / 2) + OffsetY;
}
PaintBackground(e, x, y);
PaintText(e, x, y, size, size);
PaintMarks(e, x, y);
PaintNeedle(e, x, y);
}
protected virtual void PaintBackground(PaintEventArgs e, float x, float y)
{
int size = ClientSize.Height - GaugeThickness;
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
//Draw background
using (Pen p = new Pen(GaugeColor, GaugeThickness))
{
e.Graphics.DrawArc(p, x, y, size, size, StartAngle, SweepAngle);
}
}
protected virtual void PaintText(PaintEventArgs e, float x, float y, int width, int height)
{
TextFormatFlags horizontal = TextFormatFlags.HorizontalCenter;
TextFormatFlags vertical = TextFormatFlags.VerticalCenter;
if (this.TextAlignmentHorizontal == HorizontalAlignment.Left)
{
horizontal = TextFormatFlags.Left;
}
else if (this.TextAlignmentHorizontal == HorizontalAlignment.Right)
{
horizontal = TextFormatFlags.Right;
}
if (this.TextAlignmentVertical == VerticalAlignment.Top)
{
vertical = TextFormatFlags.Top;
}
else if (this.TextAlignmentVertical == VerticalAlignment.Bottom)
{
vertical = TextFormatFlags.Bottom;
}
Rectangle rect = new Rectangle((int)x, (int)y, width, height);
e.Graphics.FillEllipse(new SolidBrush(Color.Transparent), rect);
TextRenderer.DrawText(e.Graphics, this.Text, this.Font, rect,
this.ForeColor, horizontal | vertical);
}
protected virtual void PaintMarks(PaintEventArgs e, float x, float y)
{
int size = ClientSize.Height - GaugeThickness;
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
//Draw Marks
float markLocation = 0.0f;
float spacing = SweepAngle / 10;
for (int i = 0; i < 11; i++)
{
using (Pen p = new Pen(MarkColor, MarkLength))
{
p.Alignment = PenAlignment.Inset;
e.Graphics.DrawArc(p, x, y, size, size, markLocation + StartAngle, MarkThickness);
markLocation += spacing;
}
}
}
protected virtual void PaintNeedle(PaintEventArgs e, float x, float y)
{
int size = ClientSize.Height - GaugeThickness;
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
//Draw needle.
if (Value >= MaxValue) Value = MaxValue;
using (Pen p = new Pen(NeedleColor, NeedleLength))
{
p.Alignment = PenAlignment.Inset;
e.Graphics.DrawArc(p, x, y, size, size, StartAngle + GetNeedleAngle(), NeedleSweep);
}
base.OnPaint(e);
}
I really don't have the slightest clue what would be causing the entire control to not paint at all until you click it.
I already checked Display.Designer.CS and both controls are added to "Display.Controls".
Does anyone see or know anything that I don't?
I ran Debug and the OnPaint function literally never gets called on the GPU gauge until the gauge loses/gains focus.
Here is my entire DialGauge class:
public class DialGauge : Control
{
#region Properties
[Browsable(true)]
public float StartAngle { get => _startAngle; set { _startAngle = value; Invalidate(); } }
[Browsable(true)]
public float SweepAngle { get => _sweepAngle; set { _sweepAngle = value; Invalidate(); } }
[Browsable(true)]
[DefaultValue(100d)]
public double MaxValue { get => _maxvalue; set { _maxvalue = value; Invalidate(); } }
[Browsable(true)]
public int GaugeThickness { get => _gaugethickness; set { _gaugethickness = value; Invalidate(); } }
[Browsable(true)]
[DefaultValue(150)]
public int NeedleLength { get => _needlelength; set { _needlelength = value; Invalidate(); } }
[Browsable(true)]
[DefaultValue(5)]
public float NeedleSweep { get => _needlesweep; set { _needlesweep = value; Invalidate(); } }
[Browsable(true)]
[DefaultValue(0.0)]
public double Value { get => _value; set { _value = value; Invalidate(); } }
[Browsable(true)]
public Color GaugeColor { get => gaugecolor; set { gaugecolor = value; Invalidate(); } }
[Browsable(true)]
public Color NeedleColor { get => needlecolor; set { needlecolor = value; Invalidate(); } }
[Browsable(true)]
public float MarkLength { get => _marklength; set { _marklength = value; Invalidate(); } }
[Browsable(true)]
public Color MarkColor { get => _markcolor; set { _markcolor = value; Invalidate(); } }
[Browsable(true)]
public float MarkThickness { get => _markthickness; set { _markthickness = value; Invalidate(); } }
[Browsable(true)]
[DefaultValue(HorizontalAlignment.Center)]
public HorizontalAlignment HorizontalAlignment { get => _horizontalalignment; set { _horizontalalignment = value; Invalidate(); } }
[Browsable(true)]
[DefaultValue(VerticalAlignment.Center)]
public VerticalAlignment VerticalAlignment { get => _verticalalignment; set { _verticalalignment = value; Invalidate(); } }
[Browsable(true)]
[DefaultValue(0f)]
public float OffsetX { get => _offset_x; set { _offset_x = value; Invalidate(); } }
[Browsable(true)]
[DefaultValue(0f)]
public float OffsetY { get => _offset_y; set { _offset_y = value; Invalidate(); } }
[Browsable(true)]
[DefaultValue(HorizontalAlignment.Center)]
public HorizontalAlignment TextAlignmentHorizontal { get => _texthorizontalalignment; set { _texthorizontalalignment = value; Invalidate(); } }
[Browsable(true)]
[DefaultValue(VerticalAlignment.Center)]
public VerticalAlignment TextAlignmentVertical { get => _textverticalalignment; set { _textverticalalignment = value; Invalidate(); } }
[Browsable(true)]
public override string Text { get => base.Text; set { base.Text = value; Invalidate(); } }
[Browsable(true)]
public override Font Font { get => base.Font; set { base.Font = value; Invalidate(); } }
protected float _startAngle;
protected float _sweepAngle;
protected double _maxvalue;
protected int _gaugethickness;
protected int _needlelength;
protected float _needlesweep;
protected double _value;
protected Color gaugecolor;
protected Color needlecolor;
protected float _marklength;
protected Color _markcolor;
protected float _markthickness;
protected HorizontalAlignment _horizontalalignment;
protected VerticalAlignment _verticalalignment;
protected HorizontalAlignment _texthorizontalalignment;
protected VerticalAlignment _textverticalalignment;
protected float _offset_x;
protected float _offset_y;
#endregion
public DialGauge()
{
this.SetStyle(ControlStyles.SupportsTransparentBackColor, true);
DoubleBuffered = true;
}
#region PAINT
protected override void OnPaint(PaintEventArgs e)
{
int size = ClientSize.Height - GaugeThickness;
float x = (GaugeThickness / 2) + OffsetX;
float y = (GaugeThickness / 2) + OffsetY;
if (this.HorizontalAlignment == HorizontalAlignment.Left)
{
x = ((size / 2) * -1) + OffsetX;
}
else if (this.HorizontalAlignment == HorizontalAlignment.Right)
{
x = (size / 2) + OffsetX;
}
if (this.VerticalAlignment == VerticalAlignment.Top)
{
y = ((size / 2) * -1) + OffsetY;
}
else if (this.VerticalAlignment == VerticalAlignment.Bottom)
{
y = (size / 2) + OffsetY;
}
PaintBackground(e, x, y);
PaintText(e, x, y, size, size);
PaintMarks(e, x, y);
PaintNeedle(e, x, y);
}
protected virtual void PaintBackground(PaintEventArgs e, float x, float y)
{
int size = ClientSize.Height - GaugeThickness;
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
//Draw background
using (Pen p = new Pen(GaugeColor, GaugeThickness))
{
e.Graphics.DrawArc(p, x, y, size, size, StartAngle, SweepAngle);
}
}
protected virtual void PaintText(PaintEventArgs e, float x, float y, int width, int height)
{
TextFormatFlags horizontal = TextFormatFlags.HorizontalCenter;
TextFormatFlags vertical = TextFormatFlags.VerticalCenter;
if (this.TextAlignmentHorizontal == HorizontalAlignment.Left)
{
horizontal = TextFormatFlags.Left;
}
else if (this.TextAlignmentHorizontal == HorizontalAlignment.Right)
{
horizontal = TextFormatFlags.Right;
}
if (this.TextAlignmentVertical == VerticalAlignment.Top)
{
vertical = TextFormatFlags.Top;
}
else if (this.TextAlignmentVertical == VerticalAlignment.Bottom)
{
vertical = TextFormatFlags.Bottom;
}
Rectangle rect = new Rectangle((int)x, (int)y, width, height);
e.Graphics.FillEllipse(new SolidBrush(Color.Transparent), rect);
TextRenderer.DrawText(e.Graphics, this.Text, this.Font, rect,
this.ForeColor, horizontal | vertical);
}
protected virtual void PaintMarks(PaintEventArgs e, float x, float y)
{
int size = ClientSize.Height - GaugeThickness;
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
//Draw Marks
float markLocation = 0.0f;
float spacing = SweepAngle / 10;
for (int i = 0; i < 11; i++)
{
using (Pen p = new Pen(MarkColor, MarkLength))
{
p.Alignment = PenAlignment.Inset;
e.Graphics.DrawArc(p, x, y, size, size, markLocation + StartAngle, MarkThickness);
markLocation += spacing;
}
}
}
protected virtual void PaintNeedle(PaintEventArgs e, float x, float y)
{
int size = ClientSize.Height - GaugeThickness;
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
//Draw needle.
if (Value >= MaxValue) Value = MaxValue;
using (Pen p = new Pen(NeedleColor, NeedleLength))
{
p.Alignment = PenAlignment.Inset;
e.Graphics.DrawArc(p, x, y, size, size, StartAngle + GetNeedleAngle(), NeedleSweep);
}
base.OnPaint(e);
}
#endregion
protected float GetNeedleAngle()
{
float percentage = (float)(Value / MaxValue);
float angle = SweepAngle * percentage;
return angle;
}
}

How to Reset Time of an analog clock by dragging it's handles C#

I'm very new to C#, the aim here is to edit the Time of an analog Clock by dragging it's handles. https://code.msdn.microsoft.com/windowsapps/Analog-Clock-Control-0e8ffcab#content this code has inpired me. I have three simple functions MouseDown, MouseMove and MouseUp but still I can not get Drag to work. Any suggestions please ?
public partial class Form1 : Form
{
#region Construct the clock
public Point Start { get; set; }
public Point End { get; set; }
public Form1()
{
InitializeComponent();
DoubleBuffered = true;
//Create the timer and start it
ClockTimer.Tick += ClockTimer_Tick;
ClockTimer.Enabled = true;
ClockTimer.Interval = 1;
ClockTimer.Start();
Start = p1;
End = p2;
}
#endregion
#region Update the clock
private void ClockTimer_Tick(object sender, EventArgs e)
{
Refresh();
}
private Timer ClockTimer = new Timer();
private Pen circle = new Pen(Color.Black, 2);
private Pen secondHandle = new Pen(Color.Red, 1);
private Pen minHandle = new Pen(Color.Black, 5);
private Pen hrHandle = new Pen(Color.Black, 5);
private Point p1;
private Point p2;
#endregion
#region On paint
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
//Clear the graphics to the back color of the control
pe.Graphics.Clear(BackColor);
//Draw the border of the clock
pe.Graphics.DrawEllipse(circle, 0, 0, 300, 300);
//Find the radius of the control by dividing the width by 2
float radius = (300 / 2);
//Find the origin of the circle by dividing the width and height of the control
PointF origin = new PointF(300 / 2, 300 / 2);
//Draw only if ShowMajorSegments is true;
if (ShowMajorSegments)
{
//Draw the Major segments for the clock
for (float i = 0f; i != 390f; i += 30f)
{
pe.Graphics.DrawLine(Pens.White, PointOnCircle(radius - 1, i, origin), PointOnCircle(radius - 21, i, origin));
}
}
//Draw only if ShowMinorSegments is true
if (ShowMinorSegments)
{
//Draw the minor segments for the control
for (float i = 0f; i != 366f; i += 6f)
{
pe.Graphics.DrawLine(Pens.Black, PointOnCircle(radius, i, origin), PointOnCircle(radius - 10, i, origin));
}
}
//Draw only if ShowSecondHand is true
if (ShowSecondhand)
//Draw the second hand
pe.Graphics.DrawLine(secondHandle, origin, PointOnCircle(radius, DateTime.Now.Second * 6f, origin));
//Draw only if ShowMinuteHand is true
if (ShowMinuteHand)
//Draw the minute hand
pe.Graphics.DrawLine(minHandle, origin, PointOnCircle(radius * 0.75f, DateTime.Now.Minute * 6f, origin));
minHandle.StartCap = LineCap.RoundAnchor;
minHandle.EndCap = LineCap.ArrowAnchor;
pe.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
pe.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
//Draw only if ShowHourHand is true
if (ShowHourHand)
//Draw the hour hand
pe.Graphics.DrawLine(hrHandle, origin, PointOnCircle(radius * 0.50f, DateTime.Now.Hour * 30f, origin));
hrHandle.StartCap = LineCap.RoundAnchor;
hrHandle.EndCap = LineCap.ArrowAnchor;
pe.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
pe.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
}
#endregion
#region On size changed
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
//Make sure the control is square
if (Size.Height != Size.Width)
Size = new Size(Size.Width, Size.Width);
//Redraw the control
Refresh();
}
#endregion
#region Point on circle
private PointF PointOnCircle(float radius, float angleInDegrees, PointF origin)
{
//Find the x and y using the parametric equation for a circle
float x = (float)(radius * Math.Cos((angleInDegrees - 90f) * Math.PI / 180F)) + origin.X;
float y = (float)(radius * Math.Sin((angleInDegrees - 90f) * Math.PI / 180F)) + origin.Y;
return new PointF(x, y);
}
#endregion
#region Show Minor Segments
private bool showMinorSegments = true;
public bool ShowMinorSegments
{
get
{
return showMinorSegments;
}
set
{
showMinorSegments = value;
Refresh();
}
}
#endregion
#region Show Major Segments
private bool showMajorSegments = true;
public bool ShowMajorSegments
{
get
{
return showMajorSegments;
}
set
{
showMajorSegments = value;
Refresh();
}
}
#endregion
#region Show Second Hand
private bool showSecondHand = false;
public bool ShowSecondhand
{
get
{
return showSecondHand;
}
set
{
showSecondHand = value;
Refresh();
}
}
#endregion
#region Show Minute Hand
private bool showMinuteHand = true;
public bool ShowMinuteHand
{
get
{
return showMinuteHand;
}
set
{
showMinuteHand = value;
Refresh();
}
}
#endregion
#region Show Hour Hand
private bool showHourHand = true;
public bool ShowHourHand
{
get
{
return showHourHand;
}
set
{
showHourHand = value;
Refresh();
}
}
#endregion
public float slope
{
get
{
return (((float)p2.Y - (float)p1.Y) / ((float)p2.X - (float)p1.X));
}
}
public float YIntercept
{
get
{
return p1.Y - slope * p1.X;
}
}
public bool IsPointOnLine(Point p, int cushion)
{
float temp = (slope * p.X + YIntercept);
if (temp >= (p.Y - cushion) && temp <= (p.Y + cushion))
{
return true;
}
else
{
return false;
}
}
Point deltaStart;
Point deltaEnd;
bool dragging = false;
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left && IsPointOnLine(e.Location, 5))
{
dragging = true;
deltaStart = new Point(p1.X - e.Location.X, p1.Y - e.Location.Y);
deltaEnd = new Point(p2.X - e.Location.X, p2.Y - e.Location.Y);
}
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (dragging && deltaStart != null && deltaEnd != null)
{
p1 = new Point(deltaStart.X + e.Location.X, deltaStart.Y + e.Location.Y);
p2 = new Point(deltaEnd.X + e.Location.X, deltaEnd.Y + e.Location.Y);
this.Refresh();
}
}
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
dragging = false;
}
}
I give a partial answer about translating a X, Y coordinate to an angle (in degree) based on a circle, where the 0° angle is located at the top.
(Scroll down for a compact solution)
Following the directions of typical GUI coordinates, the absolute 0,0 Point is located top left, positive X values stretch to the right and positive Y values stretch to the bottom.
In order to simplify the math, I use a virtual 0,0 point at the center of the circle, so all coordinates need to be translated to locals before calculation and to globals before actual drawing.
Coordinate overview (imagine the circle around 0; 0):
(0;-1)
(-1; 0) (0; 0) (1; 0)
(0; 1)
Now the task is for any coordinate (X; Y) to find the clock-wise angle between the line (0; 0) - (0; -1) and the line (0; 0) - (X; Y)
The circle can be divided into 4 quarter-circles, each covering a combination of signed (X; Y) values.
Quarter 1 contains the angle values 0° to 90° and is represented by positive X values and negative Y values.
Quarter 2 contains the angle values 90° to 180° and is represented by positive X values and positive Y values.
Quarter 3 contains the angle values 180° to 270° and is represented by negative X values and positive Y values.
Quarter 4 contains the angle values 270° to 360° and is represented by negative X values and negative Y values.
Note that for the corner cases 0°, 90°, 180°, 270°, 360° it doesn't really matter which of the two quarters they are assigned to.
The easiest way to understand such problems is to stick to the normal circle -> read: to normalize the X; Y coordinate to a length of 1. Additionally I go with positive values (it would also work without, but a bit differently in the + and - combinations):
var len = Math.Sqrt(X * X + Y * Y);
var xNorm = Math.Abs(X) / len;
var yNorm = Math.Abs(Y) / len;
Now, the reverse sine / cosine can be used to translate the normalized coordinates back into angle values (there's some redundancy in my calculation for the sake of simplicity and completeness):
var angleFromX = Math.Asin(xNorm) * 180.0 / Math.PI;
var angleFromY = Math.Asin(yNorm) * 180.0 / Math.PI;
Now lets apply the appropriate angle for each of the quarter circle areas
var resultAngle = 0.0;
if (quarter_1)
{
resultAngle = 0 + angleFromX;
// same as
resultAngle = 90 - angleFromY;
}
if (quarter_2)
{
resultAngle = 90 + angleFromY;
// same as
resultAngle = 180 - angleFromX;
}
if (quarter_3)
{
resultAngle = 180 + angleFromX;
// same as
resultAngle = 270 - angleFromY;
}
if (quarter_4)
{
resultAngle = 270 + angleFromY;
// same as
resultAngle = 360 - angleFromX;
}
Ofcourse, the quarter_1 - quarter_4 are pseudo-variables that represent the quarter selection as explained.
A more compact solution can be found by analyzing the different properties of the full solution.
var angleFromYAxis = Math.Asin(Y / Math.Sqrt(X * X + Y * Y)) * 180.0 / Math.PI;
var resultAngle = 0.0;
if (X >= 0)
{
resultAngle = 90 + angleFromYAxis;
}
else
{
resultAngle = 270 - angleFromYAxis;
}

Rotating an image according to two points

I have two points on my form, and a picturebox, like this:
*
[^]
[ ]
*
I would like to align the picturebox with the points, so that it looks like this:
*
\^\
\ \
*
How would I calculate the angle and how would I rotate the PictureBox?
Currently I'm using this:
double xDifference = Math.Abs(point2.X - point1.X);
double yDifference = Math.Abs(point2.Y - point1.Y);
double angle = Math.Atan(yDifference / xDifference) * 180 / Math.PI;
But that doesn't work since the x and y values are absolute, and thus they can't calculate it if point 2 is left of point 1.
To rotate the image, I found the following function:
public Bitmap rotateImage(Image image, PointF offset, float angle) {
// Create a new empty bitmap to hold rotated image
Bitmap rotatedBmp = new Bitmap(image.Width, image.Height);
rotatedBmp.SetResolution(image.HorizontalResolution, image.VerticalResolution);
// Make a graphics object from the empty bitmap
Graphics g = Graphics.FromImage(rotatedBmp);
// Put the rotation point in the center of the image
g.TranslateTransform(offset.X, offset.Y);
// Rotate the image
g.RotateTransform(angle);
// Move the image back
g.TranslateTransform(-offset.X, -offset.Y);
// Draw passed in image onto graphics object
g.DrawImage(image, new PointF(0, 0));
return rotatedBmp;
}
How would I use that function? I'm not sure what values to insert for offset.
Thanks
I do not like to use angle when it is not necessary.
Here, you just want to change of orthonormal basis.
From {X;Y} you want to move to {U;V} where V (of norm 1) is parallel to AB (or point1 point2).
Because {U;V} is an orthonormal basis, Ux = Vy and Uy = -Vx.
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace CsiChart
{
public partial class CustomControl1 : Control
{
private const float EPSILON = 1e-6f;
private Image _image;
private ImageLayout _imageLayout = ImageLayout.Center;
private PointF _pointA = new PointF(0, 100);
private PointF _pointB = new PointF(100, 0);
public CustomControl1()
{
InitializeComponent();
}
public Image Image
{
get { return _image; }
set
{
if (Equals(_image, value)) return;
_image = value;
Invalidate();
OnImageChanged(EventArgs.Empty);
}
}
public event EventHandler ImageChanged;
public ImageLayout ImageLayout
{
get { return _imageLayout; }
set
{
if (Equals(_imageLayout, value)) return;
_imageLayout = value;
Invalidate();
OnImageLayoutChanged(EventArgs.Empty);
}
}
public event EventHandler ImageLayoutChanged;
public PointF PointA
{
get { return _pointA; }
set
{
if (Equals(_pointA, value)) return;
_pointA = value;
Invalidate();
OnPointAChanged(EventArgs.Empty);
}
}
public event EventHandler PointAChanged;
public PointF PointB
{
get { return _pointB; }
set
{
if (Equals(_pointB, value)) return;
_pointB = value;
Invalidate();
OnPointBChanged(EventArgs.Empty);
}
}
public event EventHandler PointBChanged;
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
if (DesignMode) return;
var g = pe.Graphics;
g.Clear(BackColor);
var image = Image;
if (image == null) return;
var clientRectangle = ClientRectangle;
var centerX = clientRectangle.X + clientRectangle.Width / 2;
var centerY = clientRectangle.Y + clientRectangle.Height / 2;
var srcRect = new Rectangle(new Point(0, 0), image.Size);
var pointA = PointA;
var pointB = PointB;
// Compute U, AB vector normalized.
var vx = pointB.X - pointA.X;
var vy = pointB.Y - pointA.Y;
var vLength = (float) Math.Sqrt(vx*vx + vy*vy);
if (vLength < EPSILON)
{
vx = 0;
vy = 1;
}
else
{
vx /= vLength;
vy /= vLength;
}
var oldTransform = g.Transform;
// Change basis to U,V
// We also take into acount the inverted on screen Y.
g.Transform = new Matrix(-vy, vx, -vx, -vy, centerX, centerY);
var imageWidth = image.Width;
var imageHeight = image.Height;
RectangleF destRect;
switch (ImageLayout)
{
case ImageLayout.None:
destRect = new Rectangle(0, 0, imageWidth, imageHeight);
break;
case ImageLayout.Center:
destRect = new Rectangle(-imageWidth/2, -imageHeight/2, imageWidth, imageHeight);
break;
case ImageLayout.Zoom:
// XY aligned bounds size of the image.
var imageXSize = imageWidth*Math.Abs(vy) + imageHeight*Math.Abs(vx);
var imageYSize = imageWidth*Math.Abs(vx) + imageHeight*Math.Abs(vy);
// Get best scale to fit.
var s = Math.Min(clientRectangle.Width/imageXSize, clientRectangle.Height/imageYSize);
destRect = new RectangleF(-imageWidth*s/2, -imageHeight*s/2, imageWidth*s, imageHeight*s);
break;
default:
throw new InvalidOperationException();
}
g.DrawImage(image, destRect, srcRect, GraphicsUnit.Pixel);
g.Transform = oldTransform;
}
protected virtual void OnImageChanged(EventArgs eventArgs)
{
var handler = ImageChanged;
if (handler == null) return;
handler(this, eventArgs);
}
protected virtual void OnImageLayoutChanged(EventArgs eventArgs)
{
var handler = ImageLayoutChanged;
if (handler == null) return;
handler(this, eventArgs);
}
private void OnPointAChanged(EventArgs eventArgs)
{
var handler = PointAChanged;
if (handler == null) return;
handler(this, eventArgs);
}
private void OnPointBChanged(EventArgs eventArgs)
{
var handler = PointBChanged;
if (handler == null) return;
handler(this, eventArgs);
}
}
}
Let's put all the computations together.
First of all, the direction of the line connecting the two points can be computed by
double xDifference = point2.X - point1.X;
double yDifference = point2.Y - point1.Y;
double angleRadians = Math.Atan2(yDifference, xDifference);
Then, the vertical direction (90 degrees) must be parallel to the direction considered above after the rotation, so the rotation angle is
double rotationAngleRadians = angleDegrees - Math.PI/2;
Having this angle, we can compute the bounding box's size:
double newWidth = image.Width * Math.Abs(Math.Cos(rotationAngleRadians)) +
image.Height * Math.Abs(Math.Sin(rotationAngleRadians));
double newHeight = image.Width * Math.Abs(Math.Sin(rotationAngleRadians)) +
image.Height * Math.Abs(Math.Cos(rotationAngleRadians));
Now, we need first to transform in such a way that the middle of the old image is at position 0. This makes translate transform by (-image.Width/2, -image.Height/2). Then, we apply rotation by the rotationAngleDegrees (which is rotationAngleRadians * 180 / Math.PI), as Graphics' rotation expects angle in degrees. Then, we shift the image to be in the middle of the new image, that is translate transform by (newWidth/2, newHeight/2).

Zoom center of image

I create a program that zoom in/out an image.
But it doesn't zoom to the center.
My picture box is in a panel and code is:
Image newImage = Image.FromFile("view.jpg");
pictureBox1.Width = (int)(newImage.Size.Width / ZoomLevel);
pictureBox1.Height = (int)(newImage.Size.Height / ZoomLevel);
Bitmap img = new Bitmap(newImage,
(int)(newImage.Size.Width / ZoomLevel),
(int)(newImage.Size.Height / ZoomLevel));
pictureBox1.SizeMode = PictureBoxSizeMode.CenterImage;
pictureBox1.Image = img;
I uploaded a sample project here. plz download this
I use Visual Studio 2008, C#, .Net 3.5
thanks in advance.
Reza's solution works really well, but is a little bit difficult to access because of registration being required. I have rewritten his code as a standalone control and am putting the code here for future reference.
public class ImageViewer : Panel
{
public Image Image
{
get { return this.image; }
set
{
this.image = value;
this.ZoomExtents();
this.LimitBasePoint(basePoint.X, basePoint.Y);
this.Invalidate();
}
}
private bool drag;
private float ScaleFactor = 1;
private Point basePoint;
private Image image;
private int x, y;
/// <summary>
/// Class constructor
/// </summary>
public ImageViewer()
{
this.DoubleBuffered = true;
}
/// <summary>
/// Mouse button down event
/// </summary>
protected override void OnMouseDown(MouseEventArgs e)
{
switch (e.Button)
{
case MouseButtons.Left:
this.ZoomIn();
break;
case MouseButtons.Middle:
this.drag = true;
x = e.X;
y = e.Y;
break;
case MouseButtons.Right:
this.ZoomOut();
break;
}
base.OnMouseDown(e);
}
/// <summary>
/// Mouse button up event
/// </summary>
protected override void OnMouseUp(MouseEventArgs e)
{
if (e.Button == MouseButtons.Middle)
drag = false;
base.OnMouseUp(e);
}
/// <summary>
/// Mouse move event
/// </summary>
protected override void OnMouseMove(MouseEventArgs e)
{
if (drag)
{
LimitBasePoint(basePoint.X + e.X - x, basePoint.Y + e.Y - y);
x = e.X;
y = e.Y;
this.Invalidate();
}
base.OnMouseMove(e);
}
/// <summary>
/// Resize event
/// </summary>
protected override void OnResize(EventArgs e)
{
LimitBasePoint(basePoint.X, basePoint.Y);
this.Invalidate();
base.OnResize(e);
}
/// <summary>
/// Paint event
/// </summary>
protected override void OnPaint(PaintEventArgs pe)
{
if (this.Image != null)
{
Rectangle src = new Rectangle(0, 0, Image.Width, Image.Height);
Rectangle dst = new Rectangle(basePoint.X, basePoint.Y, (int)(Image.Width * ScaleFactor), (int)(Image.Height * ScaleFactor));
pe.Graphics.DrawImage(Image, dst, src, GraphicsUnit.Pixel);
}
base.OnPaint(pe);
}
private void ZoomExtents()
{
if (this.Image != null)
this.ScaleFactor = (float)Math.Min((double)this.Width / this.Image.Width, (double)this.Height / this.Image.Height);
}
private void ZoomIn()
{
if (ScaleFactor < 10)
{
int x = (int)((this.Width / 2 - basePoint.X) / ScaleFactor);
int y = (int)((this.Height / 2 - basePoint.Y) / ScaleFactor);
ScaleFactor *= 2;
LimitBasePoint((int)(this.Width / 2 - x * ScaleFactor), (int)(this.Height / 2 - y * ScaleFactor));
this.Invalidate();
}
}
private void ZoomOut()
{
if (ScaleFactor > .1)
{
int x = (int)((this.Width / 2 - basePoint.X) / ScaleFactor);
int y = (int)((this.Height / 2 - basePoint.Y) / ScaleFactor);
ScaleFactor /= 2;
LimitBasePoint((int)(this.Width / 2 - x * ScaleFactor), (int)(this.Height / 2 - y * ScaleFactor));
this.Invalidate();
}
}
private void LimitBasePoint(int x, int y)
{
if (this.Image == null)
return;
int width = this.Width - (int)(Image.Width * ScaleFactor);
int height = this.Height - (int)(Image.Height * ScaleFactor);
if (width < 0)
{
x = Math.Max(Math.Min(x, 0), width);
}
else
{
x = width / 2;
}
if (height < 0)
{
y = Math.Max(Math.Min(y, 0), height);
}
else
{
y = height / 2;
}
basePoint = new Point(x, y);
}
}
All credit goes to Reza obviously.
I found the answer.
I Uploade it here (link doesn't work anymore )
it a sample project with drag and zoom on an image. :)
I think it is because you change the size of the picturebox.
it is centered but i can not see it because you always see all the picture.
try to remove the 2 rows the change the width and hight of the picturebox or set them to be constant like 1 or 2 or... if you want to it to be according to the picture:
first option:
Image newImage = Image.FromFile("view.jpg");
Bitmap img = new Bitmap(newImage,
(int)(newImage.Size.Width / ZoomLevel),
(int)(newImage.Size.Height / ZoomLevel));
pictureBox1.SizeMode = PictureBoxSizeMode.CenterImage;
pictureBox1.Image = img;
second option:
Image newImage = Image.FromFile("view.jpg");
pictureBox1.Width = (int)(newImage.Size.Width / 1);
pictureBox1.Height = (int)(newImage.Size.Height / 1);
Bitmap img = new Bitmap(newImage,
(int)(newImage.Size.Width / ZoomLevel),
(int)(newImage.Size.Height / ZoomLevel));
pictureBox1.SizeMode = PictureBoxSizeMode.CenterImage;
pictureBox1.Image = img;

rotating a picture continuously on a windows form

I am trying to use the combination of the timer class and a codeproject to rotate an image smoothly in a picture box control (Visual Studio 2010 C#). The problem I am having is either the picture doesn't rotate or I get a threading exception. Something about "the object is in use elsewhere". Here's the main parts of the code, I would greatly appreciate any help you can provide. Thank you.
private void Form1_Load(object sender, EventArgs e)
{
timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
timer.Start();
}
private void timer_Elapsed(object sender, EventArgs e)
{
//Graphics graphic = Graphics.FromImage(pictureBox1.Image);
//graphic.RotateTransform(45);
this.Invoke(new MethodInvoker(delegate { RotateImage(pictureBox1.Image, 10); }));
}
public static Bitmap RotateImage(Image image, float angle)
{
// center of the image
float rotateAtX = image.Width / 2;
float rotateAtY = image.Height / 2;
bool bNoClip = false;
return RotateImage(image, rotateAtX, rotateAtY, angle, bNoClip);
}
public static Bitmap RotateImage(Image image, float angle, bool bNoClip)
{
// center of the image
float rotateAtX = image.Width / 2;
float rotateAtY = image.Height / 2;
return RotateImage(image, rotateAtX, rotateAtY, angle, bNoClip);
}
public static Bitmap RotateImage(Image image, float rotateAtX, float rotateAtY, float angle, bool bNoClip)
{
int W, H, X, Y;
if (bNoClip)
{
double dW = (double)image.Width;
double dH = (double)image.Height;
double degrees = Math.Abs(angle);
if (degrees <= 90)
{
double radians = 0.0174532925 * degrees;
double dSin = Math.Sin(radians);
double dCos = Math.Cos(radians);
W = (int)(dH * dSin + dW * dCos);
H = (int)(dW * dSin + dH * dCos);
X = (W - image.Width) / 2;
Y = (H - image.Height) / 2;
}
else
{
degrees -= 90;
double radians = 0.0174532925 * degrees;
double dSin = Math.Sin(radians);
double dCos = Math.Cos(radians);
W = (int)(dW * dSin + dH * dCos);
H = (int)(dH * dSin + dW * dCos);
X = (W - image.Width) / 2;
Y = (H - image.Height) / 2;
}
}
else
{
W = image.Width;
H = image.Height;
X = 0;
Y = 0;
}
//create a new empty bitmap to hold rotated image
Bitmap bmpRet = new Bitmap(W, H);
bmpRet.SetResolution(image.HorizontalResolution, image.VerticalResolution);
//make a graphics object from the empty bitmap
Graphics g = Graphics.FromImage(bmpRet);
//Put the rotation point in the "center" of the image
g.TranslateTransform(rotateAtX + X, rotateAtY + Y);
//rotate the image
g.RotateTransform(angle);
//move the image back
g.TranslateTransform(-rotateAtX - X, -rotateAtY - Y);
//draw passed in image onto graphics object
g.DrawImage(image, new PointF(0 + X, 0 + Y));
return bmpRet;
}
I think you should invalidate and paint in you OnPaint or OnDraw event
private void timer_Elapsed(object sender, EventArgs e)
{
//Graphics graphic = Graphics.FromImage(pictureBox1.Image);
//graphic.RotateTransform(45);
// this.Invoke(new MethodInvoker(delegate { RotateImage(pictureBox1.Image, 10); }));
pictureBox1.Invalidate();
}
In your form you should enable this styles for a better perfomance and smoothness
SetStyle( ControlStyles.ResizeRedraw, true );
SetStyle( ControlStyles.UserPaint, true );
SetStyle( ControlStyles.AllPaintingInWmPaint, true );
SetStyle( ControlStyles.OptimizedDoubleBuffer, true );

Categories

Resources