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;
Related
I am working on a project for school, we need to make a basic top down race game in C# without using XNA.
First of all let me tell you that the stuff we have learned about programming so far has little to do with making something that even remotely looks like a racegame. It didn't get any more difficult than array's, loops etc.
So we didn't learn about graphics or anything like that.
Having said all that I am having the following problem.
We have created a Graphics object, and then use DrawImage and use a bitmap from a car.jpg.
graphics = e.Graphics;
graphics.RotateTransform(angle);
graphics.DrawImage(car, xPos, yPos, car.Width, car.Height);
Then we wait for a key press e.g Right
case Keys.Right:
if (angle != 360)
{
angle += 10;
}
else
{
angle = 0;
}
this.Refresh();
break;
The problem we have is that the pivot point for the rotation is in the top left corner. So as soon as we move the car to something like (20,25) and start to rotate it, it will use (0,0) as the center of rotation. What we want to achieve is to have the center point of rotation at the center of our car.
We have tried looking for ways to change the centerX and centerY of the RotateTransform but have come to the conclusion that this isn't possible with the bitmap.
We have been struggling with this problem for over 2 days and can't seem to find any solution for achieving the thing we want.
Is there something we are doing wrong creating the Graphics object, or is there a totally different way to change centerX and centerY for the car?
To draw a rotated Bitmap you need to do a few steps to prepare the Graphics object:
first you move its origin onto the midpoint of the rotation
then you rotate by the desired angle
next you move it back
now you can draw the Bitmap
finally you reset the Graphics
This needs to be done for each bitmap.
Here are the steps in code to draw a Bitmap bmp at position (xPos, yPos):
float moveX = bmp.Width / 2f + xPos;
float moveY = bmp.Height / 2f+ xPosf;
e.Graphics.TranslateTransform(moveX , moveY );
e.Graphics.RotateTransform(angle);
e.Graphics.TranslateTransform(-moveX , -moveY );
e.Graphics.DrawImage(bmp, xPos, yPos);
e.Graphics.ResetTransform();
There is one possible complication: If your Bitmap has different dpi resolution than the screen i.e. than the Graphics you must first adapt the Bitmap's dpi setting!
To adapt the Bitmapto the usual 96dpi you can simply do a
bmp.SetResolution(96,96);
To be prepared for future retina-like displays you can create a class variable you set at startup:
int ScreenDpi = 96;
private void Form1_Load(object sender, EventArgs e)
{
using (Graphics G = this.CreateGraphics()) ScreenDpi = (int)G.DpiX;
}
and use it after loading the Bitmap:
bmp.SetResolution(ScreenDpi , ScreenDpi );
As usual the DrawImage method uses the top left corner of the Bitmap. You may need to use different Points for the rotation point and possibly also for the virtual position of your car, maybe in the middle of its front..
Here is static class which will paint the image in desired location within desired area. Change the rotationangle value to rotate the image. And you can also pan and zoom the image.
Add this class in your Project and call the static functions from Win Form.
public static class FullImage
{
public static Image image;
public static RectangleF DisplayRect, SourceRect;
public static Size ParentBoundry;
public static float rotationangle=0;
internal static void Paint(Graphics graphics)
{
if (image == null)
return;
float hw = DisplayRect.X + DisplayRect.Width / 2f;
float hh = DisplayRect.Y + DisplayRect.Height / 2f;
System.Drawing.Drawing2D.Matrix m = graphics.Transform;
m.RotateAt(rotationangle, new PointF(hw, hh), System.Drawing.Drawing2D.MatrixOrder.Append);
graphics.Transform = m;
graphics.DrawImage(image, new RectangleF(DisplayRect.X, DisplayRect.Y, DisplayRect.Width, DisplayRect.Height), SourceRect, GraphicsUnit.Pixel);
graphics.ResetTransform();
}
public static void LoadImage(Image img)
{
image = img;
SizeF s = GetResizedSize(image, ParentBoundry);
SourceRect = new RectangleF(0, 0, image.Width, image.Height);
DisplayRect = new RectangleF(ParentBoundry.Width / 2 - s.Width / 2, ParentBoundry.Height / 2 - s.Height / 2, s.Width, s.Height);
}
public static Size GetResizedSize(Image ImageToResize, Size size)
{
int sourceWidth = ImageToResize.Width;
int sourceHeight = ImageToResize.Height;
float nPercent = 0;
float nPercentW = 0;
float nPercentH = 0;
nPercentW = ((float)size.Width / (float)sourceWidth);
nPercentH = ((float)size.Height / (float)sourceHeight);
if (nPercentH < nPercentW)
nPercent = nPercentH;
else
nPercent = nPercentW;
int destWidth = (int)(sourceWidth * nPercent);
int destHeight = (int)(sourceHeight * nPercent);
return new Size(destWidth, destHeight);
}
internal static void MouseWheel(int delta)
{
if (delta > 0)
DisplayRect = ZoomImage(DisplayRect,CurrentMouse, .1f);
else
DisplayRect = ZoomImage(DisplayRect, CurrentMouse, -.1f);
}
private RectangleF ZoomImage(RectangleF ImageRectangle, PointF MouseLocation, float ScaleFactor)
{
/// Original Size and Location
SizeF OriginalSize = ImageRectangle.Size;
PointF OriginalPoint = ImageRectangle.Location;
///Mouse cursor location -located in width% and height% of totaloriginal image
float mouse_widthpercent = System.Math.Abs(OriginalPoint.X - MouseLocation.X) / OriginalSize.Width * 100;
float mouse_heightpercent = System.Math.Abs(OriginalPoint.Y - MouseLocation.Y) / OriginalSize.Height * 100;
///Zoomed Image by scalefactor
SizeF FinalSize = new SizeF(OriginalSize.Width + OriginalSize.Width * ScaleFactor, OriginalSize.Height + OriginalSize.Height * ScaleFactor);
if (FinalSize.Width < 15 || FinalSize.Height < 15)
return ImageRectangle;
if (FinalSize.Width > 60000 || FinalSize.Height > 60000)
return ImageRectangle;
/// How much width increases and height increases
float widhtincrease = FinalSize.Width - OriginalSize.Width;
float heightincrease = FinalSize.Height - OriginalSize.Height;
/// Adjusting Image location after zooming the image
PointF FinalLocation = new System.Drawing.PointF(OriginalPoint.X - widhtincrease * mouse_widthpercent / 100,
OriginalPoint.Y - heightincrease * mouse_heightpercent / 100);
ImageRectangle = new RectangleF(FinalLocation.X, FinalLocation.Y, FinalSize.Width, FinalSize.Height);
return ImageRectangle;
}
static bool drag = false;
static Point Initial, CurrentMouse;
internal static void MouseMove(Point location)
{
CurrentMouse = location;
if (drag)
{
DisplayRect = new RectangleF(DisplayRect.X + location.X - Initial.X, DisplayRect.Y + location.Y - Initial.Y, DisplayRect.Width, DisplayRect.Height);
Initial = location;
}
}
internal static void MouseDown(Point location)
{
Initial = location;
drag = true;
}
internal static void MouseUp(Point location)
{
drag = false;
}
}
After Adding this code in your project (Better add in separate cs file), Call the functions from Win Form class (Form1.cs).
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.DoubleBuffer, true);
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
this.SetStyle(ControlStyles.ResizeRedraw, true);
FullImage.ParentBoundry = new Size(this.Width, this.Height);
// Enter the image path
FullImage.LoadImage(Image.FromFile(#"D:\a.jpg"));
}
//Create a paint event
private void Form1_Paint(object sender, PaintEventArgs e)
{
FullImage.Paint(e.Graphics);
}
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
Vault.FullImage.MouseDown(e.Location);
this.Invalidate();
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
Vault.FullImage.MouseMove(e.Location);
this.Invalidate();
}
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
Vault.FullImage.MouseUp(e.Location);
this.Invalidate();
}
protected override void OnMouseWheel(MouseEventArgs e)
{
Vault.FullImage.MouseWheel(e.Delta);
this.Invalidate();
}
Now, if you want to rotate the image, just set the value however you want (with slider, button or add some more functions to detect the mouse movement and then rotate)
Example: add a button and each time the button clicked increase the value by 1.
private void button1_clicked(object sender, EventArgs e)
{
FullImage.rotationangle++;
this.invalidate();
}
To rotate the top left from the center you first need to know the angle of it then adjust it by the angle you want and re-calculate the new top left by the new angle:
var newXPos = (int)(xPos + car.Width / 2.0 + Math.Cos(Math.Atan2(-car.Height / 2, -car.Width / 2)
+ angle / 180.0 * Math.PI) * -car.Width / 2.0);
var newYPos = (int)(yPos + car.Height / 2.0 + Math.Sin(Math.Atan2(-car.Height / 2, -car.Width / 2)
+ angle / 180.0 * Math.PI) * -car.Height / 2.0);
graphics = e.Graphics;
graphics.RotateTransform(angle);
graphics.DrawImage(car, newXPos, newYPos, car.Width, car.Height);
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;
}
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).
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);
In C#.NET I am trying to programmatically change the color of the border in a group box.
Update: This question was asked when I was working on a winforms system before we switched to .NET.
Just add paint event.
private void groupBox1_Paint(object sender, PaintEventArgs e)
{
GroupBox box = sender as GroupBox;
DrawGroupBox(box, e.Graphics, Color.Red, Color.Blue);
}
private void DrawGroupBox(GroupBox box, Graphics g, Color textColor, Color borderColor)
{
if (box != null)
{
Brush textBrush = new SolidBrush(textColor);
Brush borderBrush = new SolidBrush(borderColor);
Pen borderPen = new Pen(borderBrush);
SizeF strSize = g.MeasureString(box.Text, box.Font);
Rectangle rect = new Rectangle(box.ClientRectangle.X,
box.ClientRectangle.Y + (int)(strSize.Height / 2),
box.ClientRectangle.Width - 1,
box.ClientRectangle.Height - (int)(strSize.Height / 2) - 1);
// Clear text and border
g.Clear(this.BackColor);
// Draw text
g.DrawString(box.Text, box.Font, textBrush, box.Padding.Left, 0);
// Drawing Border
//Left
g.DrawLine(borderPen, rect.Location, new Point(rect.X, rect.Y + rect.Height));
//Right
g.DrawLine(borderPen, new Point(rect.X + rect.Width, rect.Y), new Point(rect.X + rect.Width, rect.Y + rect.Height));
//Bottom
g.DrawLine(borderPen, new Point(rect.X, rect.Y + rect.Height), new Point(rect.X + rect.Width, rect.Y + rect.Height));
//Top1
g.DrawLine(borderPen, new Point(rect.X, rect.Y), new Point(rect.X + box.Padding.Left, rect.Y));
//Top2
g.DrawLine(borderPen, new Point(rect.X + box.Padding.Left + (int)(strSize.Width), rect.Y), new Point(rect.X + rect.Width, rect.Y));
}
}
Building on the previous answer, a better solution that includes the label for the group box:
groupBox1.Paint += PaintBorderlessGroupBox;
private void PaintBorderlessGroupBox(object sender, PaintEventArgs p)
{
GroupBox box = (GroupBox)sender;
p.Graphics.Clear(SystemColors.Control);
p.Graphics.DrawString(box.Text, box.Font, Brushes.Black, 0, 0);
}
You might want to adjust the x/y for the text, but for my use this is just right.
Just set the paint action on any object (not just buttons) to this method to draw a border.
private void UserControl1_Paint(object sender, PaintEventArgs e)
{
ControlPaint.DrawBorder(e.Graphics, this.ClientRectangle, Color.Red, ButtonBorderStyle.Solid);
}
It still wont be pretty and rounded like the original, but it is much simpler.
FWIW, this is the implementation I used. It's a child of GroupBox but allows setting not only the BorderColor, but also the thickness of the border and the radius of the rounded corners. Also, you can set the amount of indent you want for the GroupBox label, and using a negative indent indents from the right side.
using System;
using System.Drawing;
using System.Windows.Forms;
namespace BorderedGroupBox
{
public class BorderedGroupBox : GroupBox
{
private Color _borderColor = Color.Black;
private int _borderWidth = 2;
private int _borderRadius = 5;
private int _textIndent = 10;
public BorderedGroupBox() : base()
{
InitializeComponent();
this.Paint += this.BorderedGroupBox_Paint;
}
public BorderedGroupBox(int width, float radius, Color color) : base()
{
this._borderWidth = Math.Max(1,width);
this._borderColor = color;
this._borderRadius = Math.Max(0,radius);
InitializeComponent();
this.Paint += this.BorderedGroupBox_Paint;
}
public Color BorderColor
{
get => this._borderColor;
set
{
this._borderColor = value;
DrawGroupBox();
}
}
public int BorderWidth
{
get => this._borderWidth;
set
{
if (value > 0)
{
this._borderWidth = Math.Min(value, 10);
DrawGroupBox();
}
}
}
public int BorderRadius
{
get => this._borderRadius;
set
{ // Setting a radius of 0 produces square corners...
if (value >= 0)
{
this._borderRadius = value;
this.DrawGroupBox();
}
}
}
public int LabelIndent
{
get => this._textIndent;
set
{
this._textIndent = value;
this.DrawGroupBox();
}
}
private void BorderedGroupBox_Paint(object sender, PaintEventArgs e) =>
DrawGroupBox(e.Graphics);
private void DrawGroupBox() =>
this.DrawGroupBox(this.CreateGraphics());
private void DrawGroupBox(Graphics g)
{
Brush textBrush = new SolidBrush(this.ForeColor);
SizeF strSize = g.MeasureString(this.Text, this.Font);
Brush borderBrush = new SolidBrush(this.BorderColor);
Pen borderPen = new Pen(borderBrush,(float)this._borderWidth);
Rectangle rect = new Rectangle(this.ClientRectangle.X,
this.ClientRectangle.Y + (int)(strSize.Height / 2),
this.ClientRectangle.Width - 1,
this.ClientRectangle.Height - (int)(strSize.Height / 2) - 1);
Brush labelBrush = new SolidBrush(this.BackColor);
// Clear text and border
g.Clear(this.BackColor);
// Drawing Border (added "Fix" from Jim Fell, Oct 6, '18)
int rectX = (0 == this._borderWidth % 2) ? rect.X + this._borderWidth / 2 : rect.X + 1 + this._borderWidth / 2;
int rectHeight = (0 == this._borderWidth % 2) ? rect.Height - this._borderWidth / 2 : rect.Height - 1 - this._borderWidth / 2;
// NOTE DIFFERENCE: rectX vs rect.X and rectHeight vs rect.Height
g.DrawRoundedRectangle(borderPen, rectX, rect.Y, rect.Width, rectHeight, (float)this._borderRadius);
// Draw text
if (this.Text.Length > 0)
{
// Do some work to ensure we don't put the label outside
// of the box, regardless of what value is assigned to the Indent:
int width = (int)rect.Width, posX;
posX = (this._textIndent < 0) ? Math.Max(0-width,this._textIndent) : Math.Min(width, this._textIndent);
posX = (posX < 0) ? rect.Width + posX - (int)strSize.Width : posX;
g.FillRectangle(labelBrush, posX, 0, strSize.Width, strSize.Height);
g.DrawString(this.Text, this.Font, textBrush, posX, 0);
}
}
#region Component Designer generated code
/// <summary>Required designer variable.</summary>
private System.ComponentModel.IContainer components = null;
/// <summary>Clean up any resources being used.</summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
components.Dispose();
base.Dispose(disposing);
}
/// <summary>Required method for Designer support - Don't modify!</summary>
private void InitializeComponent() => components = new System.ComponentModel.Container();
#endregion
}
}
To make it work, you also have to extend the base Graphics class (Note: this is derived from some code I found on here once when I was trying to create a rounded-corners Panel control, but I can't find the original post to link here):
static class GraphicsExtension
{
private static GraphicsPath GenerateRoundedRectangle(
this Graphics graphics,
RectangleF rectangle,
float radius)
{
float diameter;
GraphicsPath path = new GraphicsPath();
if (radius <= 0.0F)
{
path.AddRectangle(rectangle);
path.CloseFigure();
return path;
}
else
{
if (radius >= (Math.Min(rectangle.Width, rectangle.Height)) / 2.0)
return graphics.GenerateCapsule(rectangle);
diameter = radius * 2.0F;
SizeF sizeF = new SizeF(diameter, diameter);
RectangleF arc = new RectangleF(rectangle.Location, sizeF);
path.AddArc(arc, 180, 90);
arc.X = rectangle.Right - diameter;
path.AddArc(arc, 270, 90);
arc.Y = rectangle.Bottom - diameter;
path.AddArc(arc, 0, 90);
arc.X = rectangle.Left;
path.AddArc(arc, 90, 90);
path.CloseFigure();
}
return path;
}
private static GraphicsPath GenerateCapsule(
this Graphics graphics,
RectangleF baseRect)
{
float diameter;
RectangleF arc;
GraphicsPath path = new GraphicsPath();
try
{
if (baseRect.Width > baseRect.Height)
{
diameter = baseRect.Height;
SizeF sizeF = new SizeF(diameter, diameter);
arc = new RectangleF(baseRect.Location, sizeF);
path.AddArc(arc, 90, 180);
arc.X = baseRect.Right - diameter;
path.AddArc(arc, 270, 180);
}
else if (baseRect.Width < baseRect.Height)
{
diameter = baseRect.Width;
SizeF sizeF = new SizeF(diameter, diameter);
arc = new RectangleF(baseRect.Location, sizeF);
path.AddArc(arc, 180, 180);
arc.Y = baseRect.Bottom - diameter;
path.AddArc(arc, 0, 180);
}
else path.AddEllipse(baseRect);
}
catch { path.AddEllipse(baseRect); }
finally { path.CloseFigure(); }
return path;
}
/// <summary>
/// Draws a rounded rectangle specified by a pair of coordinates, a width, a height and the radius
/// for the arcs that make the rounded edges.
/// </summary>
/// <param name="brush">System.Drawing.Pen that determines the color, width and style of the rectangle.</param>
/// <param name="x">The x-coordinate of the upper-left corner of the rectangle to draw.</param>
/// <param name="y">The y-coordinate of the upper-left corner of the rectangle to draw.</param>
/// <param name="width">Width of the rectangle to draw.</param>
/// <param name="height">Height of the rectangle to draw.</param>
/// <param name="radius">The radius of the arc used for the rounded edges.</param>
public static void DrawRoundedRectangle(
this Graphics graphics,
Pen pen,
float x,
float y,
float width,
float height,
float radius)
{
RectangleF rectangle = new RectangleF(x, y, width, height);
GraphicsPath path = graphics.GenerateRoundedRectangle(rectangle, radius);
SmoothingMode old = graphics.SmoothingMode;
graphics.SmoothingMode = SmoothingMode.AntiAlias;
graphics.DrawPath(pen, path);
graphics.SmoothingMode = old;
}
/// <summary>
/// Draws a rounded rectangle specified by a pair of coordinates, a width, a height and the radius
/// for the arcs that make the rounded edges.
/// </summary>
/// <param name="brush">System.Drawing.Pen that determines the color, width and style of the rectangle.</param>
/// <param name="x">The x-coordinate of the upper-left corner of the rectangle to draw.</param>
/// <param name="y">The y-coordinate of the upper-left corner of the rectangle to draw.</param>
/// <param name="width">Width of the rectangle to draw.</param>
/// <param name="height">Height of the rectangle to draw.</param>
/// <param name="radius">The radius of the arc used for the rounded edges.</param>
public static void DrawRoundedRectangle(
this Graphics graphics,
Pen pen,
int x,
int y,
int width,
int height,
int radius)
{
graphics.DrawRoundedRectangle(
pen,
Convert.ToSingle(x),
Convert.ToSingle(y),
Convert.ToSingle(width),
Convert.ToSingle(height),
Convert.ToSingle(radius));
}
}
I'm not sure this applies to every case, but thanks to this thread, we quickly hooked into the Paint event programmatically using:
GroupBox box = new GroupBox();
[...]
box.Paint += delegate(object o, PaintEventArgs p)
{
p.Graphics.Clear(someColorHere);
};
Cheers!
I have achieved same border with something which might be simpler to understand for newbies:
private void groupSchitaCentru_Paint(object sender, PaintEventArgs e)
{
Pen blackPen = new Pen(Color.Black, 2);
Point pointTopLeft = new Point(0, 7);
Point pointBottomLeft = new Point(0, groupSchitaCentru.ClientRectangle.Height);
Point pointTopRight = new Point(groupSchitaCentru.ClientRectangle.Width, 7);
Point pointBottomRight = new Point(groupSchitaCentru.ClientRectangle.Width, groupSchitaCentru.ClientRectangle.Height);
e.Graphics.DrawLine(blackPen, pointTopLeft, pointBottomLeft);
e.Graphics.DrawLine(blackPen, pointTopLeft, pointTopRight);
e.Graphics.DrawLine(blackPen, pointBottomRight, pointTopRight);
e.Graphics.DrawLine(blackPen, pointBottomLeft, pointBottomRight);
}
Set the Paint event on the GroupBox control. In this example the name of my control is "groupSchitaCentru". One needs this event because of its parameter e.
Set up a pen object by making use of the System.Drawing.Pen class : https://msdn.microsoft.com/en-us/library/f956fzw1(v=vs.110).aspx
Set the points which represent the corners of the rectangle represented by the control. Used the property ClientRectangle of the the control to get its dimensions.
I used for TopLeft (0,7) because I want to respect the borders of the control, and draw the line about the its text.
To get more information about the coordinates system walk here : https://learn.microsoft.com/en-us/dotnet/framework/winforms/windows-forms-coordinates
I do not know, may be it helps someone looking to achieve this border adjustment thing.
This tweak to Jim Fell's code placed the borders a little better for me, but it's too long to add as a comment
...
Rectangle rect = new Rectangle(this.ClientRectangle.X,
this.ClientRectangle.Y + (int)(strSize.Height / 2),
this.ClientRectangle.Width,
this.ClientRectangle.Height - (int)(strSize.Height / 2));
Brush labelBrush = new SolidBrush(this.BackColor);
// Clear text and border
g.Clear(this.BackColor);
int drawX = rect.X;
int drawY = rect.Y;
int drawWidth = rect.Width;
int drawHeight = rect.Height;
if (this._borderWidth > 0)
{
drawX += this._borderWidth / 2;
drawY += this._borderWidth / 2;
drawWidth -= this._borderWidth;
drawHeight -= this._borderWidth;
if (this._borderWidth % 2 == 0)
{
drawX -= 1;
drawWidth += 1;
drawY -= 1;
drawHeight += 1;
}
}
g.DrawRoundedRectangle(borderPen, drawX, drawY, drawWidth, drawHeight, (float)this._borderRadius);