Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rotate a Graphics bitmap at its center

Tags:

c#

graphics

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?

like image 316
BreadleyG Avatar asked Oct 23 '14 10:10

BreadleyG


2 Answers

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..

like image 95
TaW Avatar answered Oct 19 '22 20:10

TaW


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();
  }
like image 23
Sachit Devkota Avatar answered Oct 19 '22 20:10

Sachit Devkota