I am trying to draw a heat map with a given input of points and amplitude of each point. The normal input size is around 400,000 points, but the code draws every 15th point. The map is drawn onto a Canvas.
In the XAML:
<Grid x:Name="MainPanel" Width="800" Height="600">
<Textbox Name="textbox" Grid.Column="0"/>
<Canvas Name="canvas" Grid.Column="1" HorizontalAlignment="Left" Height="450" Margin="70,75,0,0" VerticalAlignment="Top" Width="706"/>
</Grid>
Rendering code:
public static void DrawPoints(Parser parser, Canvas canvas, double radiusX, double radiusY, double thickness, double minAmplitude, double maxAmplitude)
{
double cellWidth = canvas.ActualWidth;
double cellHeight = canvas.ActualHeight;
var list = parser.Data;
// Draws every 15th point
int res = 15;
for (int i = 0; i < list.Count; i += res)
{
var location = list[i].Point;
var amplitude = list[i].Amplitude;
// Converts rgb to hsv values for representation
var color= ColorConverter.hsv2rgb(
COLOR_START_H * (1.0 - ((amplitude - minAmplitude) / (maxAmplitude - minAmplitude))),
COLOR_START_S, COLOR_START_V);
Point center = new Point(location.X, location.Y);
DrawEllipse(canvas, center, radiusX, radiusY,
new SolidColorBrush(color), new SolidColorBrush(color), thickness);
}
return;
}
In the DrawEllipse function:
public static void DrawEllipse(Canvas canvas, Point center, double radiusX, double radiusY, Brush fill, Brush stroke, double thickness)
{
var ellipse = new Ellipse();
ellipse.Width = radiusX * 2;
ellipse.Height = radiusY * 2;
ellipse.Fill = fill;
ellipse.Stroke = stroke;
ellipse.StrokeThickness = thickness;
Canvas.SetLeft(ellipse, center.X - radiusX);
Canvas.SetBottom(ellipse, center.Y - radiusY);
canvas.Children.Add(ellipse);
return;
}
In the Main code:
private void btnDraw_Click(object sender, RoutedEventArgs e)
{
Painter.DrawPoints(parser, canvas, 3, 3, 1, 0, 100);
}
The issue is that after rendering all the points onto Canvas, when I try to enter some text into textbox within the same grid, there is significant lag. What can I do to reduce the lag?
What you can do, is draw all of your data into a Visual and render it with a RenderTargetBitmap. This will take a while but will solve your performance-issues.
private List<Point> _points;
private Size _targetarea;
private void btnGenerate_Click(object sender, RoutedEventArgs e)
{
_targetarea = new Size(500, 500);
_points = GeneratePoints(10000, _targetarea);
TheImage.Source = DrawPoints(_points, _targetarea, 5, 5, 1);
}
private List<Point> GeneratePoints(int count, Size size)
{
Random r = new Random();
return Enumerable.Range(0, count)
.Select(j => new Point(r.NextDouble() * size.Width, r.NextDouble() * size.Height))
.ToList();
}
private static ImageSource DrawPoints(IEnumerable<Point> points, Size size, double radiusX, double radiusY, double thickness)
{
DrawingVisual visual = new DrawingVisual();
using (DrawingContext context = visual.RenderOpen())
{
var fill = new SolidColorBrush(Colors.Yellow);
var stroke = new SolidColorBrush(Colors.Red);
foreach(var center in points)
{
context.DrawEllipse(fill, new Pen(stroke, thickness), center, radiusX, radiusY);
}
}
RenderTargetBitmap bitmap = new RenderTargetBitmap((int)size.Width, (int)size.Height, 96, 96, PixelFormats.Pbgra32);
bitmap.Render(visual);
bitmap.Freeze();
return bitmap;
}
To determine the nearest data point when you click on the image, simply use the original data that you used to generate the image.
private void TheImage_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Point clickPoint = GetCoordinates(e.GetPosition((IInputElement) sender));
Point closestPoint = new Point();
double minSquareDistance = double.MaxValue;
foreach (Point point in _points)
{
double dX = point.X - clickPoint.X;
double dY = point.Y - clickPoint.Y;
double squareDistance = dX * dX * dY * dY;
if (squareDistance < minSquareDistance)
{
minSquareDistance = squareDistance;
closestPoint = point;
}
}
MessageBox.Show($"Clicked near {closestPoint.X:f0}/{closestPoint.Y:f0}");
}
private Point GetCoordinates(Point position)
{
// Here you need to translate Screen-Coordinates to your internal coordinate system
return position;
}
To speed up the process, install WriteableBitmapEx (NuGet available) and use this instead:
private static ImageSource DrawPoints(IEnumerable<Point> points, Size size, double radiusX, double radiusY, double thickness)
{
WriteableBitmap bitmap = new WriteableBitmap((int)size.Width, (int)size.Height, 96, 96, PixelFormats.Pbgra32, null);
foreach(var center in points)
{
bitmap.FillEllipse((int)(center.X - radiusX), (int)(center.Y - radiusY), (int)(center.X + radiusX), (int)(center.Y + radiusY), Colors.Yellow);
bitmap.DrawEllipse((int) (center.X - radiusX), (int) (center.Y - radiusY), (int) (center.X + radiusX), (int) (center.Y + radiusY), Colors.Red);
}
bitmap.Freeze();
return bitmap;
}
I don't have access to the rest of your code, so the following code is using a lot of simplifications and random data e.g. I'm using an Image instead of a Canvas and simple Points instead of whatever your parser returns.
Related
i need to generate a new method to fill the triangle in the below code and call it separately
any advise please?
public void draw(Graphics g, Pen blackPen)
{
double xDiff, yDiff, xMid, yMid;
xDiff = oppPt.X - keyPt.X;
yDiff = oppPt.Y - keyPt.Y;
xMid = (oppPt.X + keyPt.X) / 2;
yMid = (oppPt.Y + keyPt.Y) / 2;
// draw triangle
g.DrawLine(blackPen, (int)keyPt.X, (int)keyPt.Y, (int)(xMid + yDiff / 2), (int)(yMid - xDiff / 2));
g.DrawLine(blackPen, (int)(xMid + yDiff / 2), (int)(yMid - xDiff / 2), (int)oppPt.X, (int)oppPt.Y);
g.DrawLine(blackPen, (int)keyPt.X, (int)keyPt.Y, oppPt.X, oppPt.Y);
}
the method should take both of these arguments
public void fillTriangle(Graphics g, Brush redBrush)
{
}
Use a single function for the drawing, and for reduced complexity and consistency use a GraphicsPath object.
void DrawGraphics(Graphics g, Pen pen, Brush brush)
{
float xDiff=oppPt.X-keyPt.X;
float yDiff=oppPt.Y-keyPt.Y;
float xMid=(oppPt.X+keyPt.X)/2;
float yMid=(oppPt.Y+keyPt.Y)/2;
// Define path with the geometry information only
var path = new GraphicsPath();
path.AddLines(new PointF[] {
keyPt,
new PointF(xMid + yDiff/2, yMid-xDiff/2),
oppPt,
});
path.CloseFigure();
// Fill Triangle
g.FillPath(brush, path);
// Draw Triangle
g.DrawPath(pen, path);
}
The result is as seen below:
You just need to create an array with the dots, after that configure how the fill will be and then yes draw FillPolygon which is an entity to DrawPolygon which is just the outline.
public void draw(Pen blackPen)
{
Graphics draw = CreateGraphics();
Point[] points = new Point[6];
points[0].X = 0;
points[0].Y = 0;
points[1].X = 150;
points[1].Y = 150;
points[2].X = 0;
points[2].Y = 150;
using (SolidBrush fillvar = new SolidBrush(Color.FromArgb(100, Color.Yellow)))
{
draw.FillPolygon(fillvar, points.ToArray());
draw.DrawPolygon(Pens.DarkBlue, points);
}
}
If you want to draw the FillPolygon inside something, as with a PictureBox you just have to assign it to the Graphics draw.
Graphics draw = picturebox.CreateGraphics();
Above is just a practical illustration of how it works, take a look at your code. Missing just implement its xDiff, yiff, xMid, yMid coordinates.
public void draw(Graphics g, Pen blackPen)
{
double xDiff, yDiff, xMid, yMid;
Point[] points = new Point[6];
points[0].X = 50;
points[0].Y = 50;
points[1].X = 150;
points[1].Y = 150;
points[2].X = 0;
points[2].Y = 150;
SolidBrush varbrush = new SolidBrush(Color.FromArgb(100, Color.Yellow));
fillTriangle(g, varbrush, points);
}
You will have to pass the dots to fillTriangle and draw them in this method.
public void fillTriangle(Graphics g, Brush varbrush, Point[] points)
{
g.FillPolygon(varbrush, points.ToArray());
g.DrawPolygon(Pens.Red, points);
}
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 have a list of Points that have been drawn on pictureBox1.
pictureBox1 has been transformed.
Now, I want to get XY coordinates of the point that was drawn as I hover over any drawn point.
When I hover over the pictureBox1, I am getting the XY of the pictureBox -- not a transformed XY.
Can you help me get to the transformed XY?
Thanks
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
int height = pictureBox1.ClientSize.Height / 2;
int width = pictureBox1.ClientSize.Width / 2;
//=====
//scale
//=====
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.TranslateTransform(-width, -height);
e.Graphics.ScaleTransform(2f, 2f);
//===========
//draw center
//===========
e.Graphics.DrawLine(new Pen(Color.Black, 0.5f), new Point(width - 2, height), new Point(width + 2, height));
e.Graphics.DrawLine(new Pen(Color.Black, 0.5f), new Point(width, height - 2), new Point(width, height + 2));
//===========
//draw points
//===========
foreach (var p in Points)
{
Point[] pts = new Point[] { new Point(p.X, p.Y) };
Rectangle rc = new Rectangle(pts[0], new Size(1, 1));
e.Graphics.DrawRectangle(Pens.Red, rc);
}
}
As a variation to #Vitaly's answer you can do this:
After transforming the Graphics object you can save its transformation matrix e.Graphics.Transform in a variable:
Matrix matrix = null;
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
int height = pictureBox1.ClientSize.Height / 2;
int width = pictureBox1.ClientSize.Width / 2;
//=====
//scale
//=====
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.TranslateTransform(-width, -height);
e.Graphics.ScaleTransform(2f, 2f);
matrix = e.Graphics.Transform; // save the transformation matrix!
...
This is necessary as the transfomation data are lost after the Paint event!
Note that the GraphicsState graphics.Save()&Restore() functions can't be used very well for this purpose, as it only puts the state on the stack for using it once, meaning it doesn't save these data in a persistent way.
Later you can use the Matrix and this function to either transform Points with the same matrix or reverse the transformation, e.g. for mouse coordinates:
PointF transformed(Point p0, bool forward)
{
Matrix m = matrix.Clone();
if (!forward) m.Invert();
var pt = new Point[] { p0 };
m.TransformPoints(pt);
return pt[0];
}
Now my MouseMove event shows the location both raw and re-transformed:
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
label1.Text = e.Location + " <-> " + transformed(e.Location, false) ;
}
And to test the forward transformation you could add this to the end of the Paint event:
e.Graphics.ResetTransform();
for (int i = 0; i < Points.Count; i++)
{
Point[] pts = new Point[] { Point.Round(transformed(Points[i], true)) };
Rectangle rc = new Rectangle(pts[0], new Size(19, 19));
e.Graphics.DrawRectangle(Pens.Red, rc);
}
This first clears all the transformations and then paints larger Rectangles at the same locations by calling the transformed function.
Note that this will also work with a rotated Graphics object. (Although the last test does not draw the larger rectangles rotated, just moved to the right locations.)
Also note that I return PointF for better precision when scaling with fractions. You can use Point.Round (or Point.Truncate) to get Point.
Do have a look the the Matrix.Elements: They contain the numbers you have used:
float scaleX = matrix.Elements[0];
float scaleY = matrix.Elements[3];
float transX = matrix.Elements[4];
float transY = matrix.Elements[5];
Finally: It is well worth studying the many methods of Matrix..!
You can create a Matrix with necessary transformations and apply it in pictureBox1_Paint(...) via MultiplyTransform(...):
https://msdn.microsoft.com/en-us/library/bt34tx5d(v=vs.110).aspx
Then you can use Matrix::TransformPoints(...) to get transformed XY
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 have this code, that draws an image.
private void timer1_Tick(object sender, EventArgs e)
{
Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
var tempRocket = new Bitmap(Properties.Resources.rocket);
using (var g = Graphics.FromImage(tempRocket))
{
e.Graphics.DrawImage(tempRocket, 150, 150);
}
}
Yet what do I do to rotate it?
public static Bitmap RotateImage(Bitmap b, float angle)
{
//create a new empty bitmap to hold rotated image
Bitmap returnBitmap = new Bitmap(b.Width, b.Height);
//make a graphics object from the empty bitmap
using(Graphics g = Graphics.FromImage(returnBitmap))
{
//move rotation point to center of image
g.TranslateTransform((float)b.Width / 2, (float)b.Height / 2);
//rotate
g.RotateTransform(angle);
//move image back
g.TranslateTransform(-(float)b.Width / 2, -(float)b.Height / 2);
//draw passed in image onto graphics object
g.DrawImage(b, new Point(0, 0));
}
return returnBitmap;
}
There are overloads of Graphics.DrawImage that take an array of three points used to define a parallelogram for the destination, such as:
Graphics.DrawImage Method (Image, Point[])
Remarks
The destPoints parameter specifies
three points of a parallelogram. The
three Point structures represent the
upper-left, upper-right, and
lower-left corners of the
parallelogram. The fourth point is
extrapolated from the first three to
form a parallelogram.
The image represented by the image
parameter is scaled and sheared to fit
the shape of the parallelogram
specified by the destPoints
parameters.
There is also an article on MSDN describing the use of this method: How to: Rotate, Reflect, and Skew Images, with the following code example. Unfortunately, the example complicates the issue by also skewing the image.
Point[] destinationPoints = {
new Point(200, 20), // destination for upper-left point of original
new Point(110, 100), // destination for upper-right point of original
new Point(250, 30)}; // destination for lower-left point of original
Image image = new Bitmap("Stripes.bmp");
// Draw the image unaltered with its upper-left corner at (0, 0).
e.Graphics.DrawImage(image, 0, 0);
// Draw the image mapped to the parallelogram.
e.Graphics.DrawImage(image, destinationPoints);
The main differences compared to using the Graphics.Transform property are:
This method does not allow you to specify the rotation angle in degrees -- you have to use some simple trigonometry to derive the points.
This transformation applies only to the specific image.
Good if you only need to draw one rotated image and everything else is non-rotated since you don't have to reset Graphics.Transform afterward.
Bad if you want to rotate several things together (i.e., rotate the "camera").
Use Graphics.RotateTransform to rotate the image.
protected override void OnPaint(PaintEventArgs e)
{
var tempRocket = new Bitmap(Properties.Resources.rocket);
e.Graphics.RotateTransform(30.0F);
e.Graphics.DrawImage(tempRocket, 150, 150);
}
Version without clipping:
private Bitmap RotateBitmap(Bitmap bitmap, float angle)
{
int w, h, x, y;
var dW = (double)bitmap.Width;
var dH = (double)bitmap.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 - bitmap.Width) / 2;
y = (h - bitmap.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 - bitmap.Width) / 2;
y = (h - bitmap.Height) / 2;
}
var rotateAtX = bitmap.Width / 2f;
var rotateAtY = bitmap.Height / 2f;
var bmpRet = new Bitmap(w, h);
bmpRet.SetResolution(bitmap.HorizontalResolution, bitmap.VerticalResolution);
using (var graphics = Graphics.FromImage(bmpRet))
{
graphics.Clear(Color.White);
graphics.TranslateTransform(rotateAtX + x, rotateAtY + y);
graphics.RotateTransform(angle);
graphics.TranslateTransform(-rotateAtX - x, -rotateAtY - y);
graphics.DrawImage(bitmap, new PointF(0 + x, 0 + y));
}
return bmpRet;
}
Primary source
You need to apply transformation matrix.
Here you can find good example about transformations in GDI+