1

I'm trying to rotate an image in a C# winforms whenever I press the keyboard. I've attempted to use this article https://www.codeproject.com/Articles/58815/C-Image-PictureBox-Rotations, and this answer https://stackoverflow.com/a/26455088/1907765 to achieve this, but the image disappears whenever I apply the transform. I'm not exactly sure how to correct it.

The image is loaded from a Resource.resx file at run-time, into a PictureBox. The PictureBox sits on top of a panel.

Listing the relevant portion of the code here:

public partial class Form1 : Form
    {
        public List<PictureBox> Walls;

        public bool MoveUp, MoveDown, MoveLeft, MoveRight;
        public bool RotateClockwise, RotateAnticlockwise;
        public bool PlayerHasShot;

        public bool GameOver = false;

        public int PlayerAngle = 0;
        public Point PlayerPos = new Point(300, 300);

        Bitmap PlayerImage;

        // Game defaults
        public readonly int BulletSpeed = 20;
        public readonly int PlayerSpeed = 5;

        public Form1()
        {
            InitializeComponent();
            // <....>
        }


        private void Form1_Load(object sender, EventArgs e)
        {
            // Load the player image from bitmap
            PbxPlayer.Parent = PnlGameBoard;

            Image img = Properties.Resources.tankbase_orig;
            PlayerImage = new Bitmap(img);

            int dpi = 96;
            using (Graphics G = PnlGameBoard.CreateGraphics())
                dpi = (int)G.DpiX;
            PlayerImage.SetResolution(dpi, dpi);

            PbxPlayer.Image = (Bitmap)PlayerImage.Clone();
            PbxPlayer.ClientSize = PlayerImage.Size;
        }

        private void MainTimer_Tick(object sender, EventArgs e)
        {

            Point newPoint = new Point();

            if (MoveUp == true)
            {
                // Move player up 1 pixel until they've reached 'PlayerSpeed' or collided with something
                for (int Y = 1; Y <= PlayerSpeed; Y++)
                {
                    newPoint = new Point(PbxPlayer.Location.X, PbxPlayer.Location.Y - 1);
                    if (DetectCollision(PbxPlayer, newPoint))
                        break;
                    PbxPlayer.Location = newPoint;
                }
            }

            if (MoveLeft == true)
            {
                // same logic as MoveUp
            }

            if (MoveDown == true)
            {
                // same logic as MoveUp
            }

            if (MoveRight == true)
            {                    
                // same logic as MoveUp
            }

            if (RotateAnticlockwise == true)
            {
                PlayerAngle -= 2;
                if (PlayerAngle < 0)
                    PlayerAngle = 360;
                // Debug label to check the angle is set correctly
                LblAngle.Text = PlayerAngle.ToString();

                // The amount to move the image by so that it rotates around centre point
                PointF offsets = new PointF(PlayerImage.Width / 2f, PlayerImage.Height / 2f);

                // Save old image
                Image oldImage = PbxPlayer.Image;
                // Return new image rotated by an angle of -2 degrees.
                PbxPlayer.Image = RotateImage(PbxPlayer.Image, offsets, PbxPlayer.Location, -2);
                // Force the picturebox to redraw (I think?)
                PbxPlayer.Invalidate();
                if (oldImage != null)
                    oldImage.Dispose();
            }

            if (RotateClockwise == true)
            {
                PlayerAngle += 2;
                PlayerAngle %= 360;
                LblAngle.Text = PlayerAngle.ToString();

                PointF offsets = new PointF(PlayerImage.Width / 2f, PlayerImage.Height / 2f);

                Image oldImage = PbxPlayer.Image;
                PbxPlayer.Image = RotateImage(PbxPlayer.Image, offsets, PbxPlayer.Location, 2);
                PbxPlayer.Invalidate();
                if (oldImage != null)
                    oldImage.Dispose();
            }

            foreach (Control ctl in PnlGameBoard.Controls)
            {
                if (ctl is PictureBox && (string)ctl.Tag == "bullet")
                {
                    ctl.Left += BulletSpeed;

                    if (ctl.Left > PnlGameBoard.Width || DetectCollision((PictureBox)ctl, ctl.Location))
                    {
                        RemoveBullet((PictureBox)ctl);
                    }
                }
            }
        }

        private void KeyIsDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.W || e.KeyCode == Keys.Up)
                MoveUp = true;

            if (e.KeyCode == Keys.A || e.KeyCode == Keys.Left)
                MoveLeft = true;

            if (e.KeyCode == Keys.S || e.KeyCode == Keys.Down)
                MoveDown = true;

            if (e.KeyCode == Keys.D || e.KeyCode == Keys.Right)
                MoveRight = true;

            if (e.KeyCode == Keys.Q || e.KeyCode == Keys.Oemcomma)
                RotateAnticlockwise = true;

            if (e.KeyCode == Keys.E || e.KeyCode == Keys.OemPeriod)
                RotateClockwise = true;

            if (e.KeyCode == Keys.Space && PlayerHasShot == false)
            {
                MakeBullet();
                PlayerHasShot = true;
            }

        }

        private void KeyIsUp(object sender, KeyEventArgs e)
        {

            if (e.KeyCode == Keys.W || e.KeyCode == Keys.Up)
                MoveUp = false;

            if (e.KeyCode == Keys.A || e.KeyCode == Keys.Left)
                MoveLeft = false;

            if (e.KeyCode == Keys.S || e.KeyCode == Keys.Down)
                MoveDown = false;

            if (e.KeyCode == Keys.D || e.KeyCode == Keys.Right)
                MoveRight = false;

            if (e.KeyCode == Keys.Q || e.KeyCode == Keys.Oemcomma)
                RotateAnticlockwise = false;

            if (e.KeyCode == Keys.E || e.KeyCode == Keys.OemPeriod)
                RotateClockwise = false;

            // Stop repetitive shots
            if (PlayerHasShot == true)
                PlayerHasShot = false;

            if (e.KeyCode == Keys.Enter && GameOver == true)
                RestartGame();
        }

        private void PnlGameBoard_Paint(object sender, PaintEventArgs e)
        {
            // I feel like I should be doing something else in this event to make the image persist
            e.Graphics.DrawImage(PbxPlayer.Image, PbxPlayer.Location);
        }

        public Bitmap RotateImage(Image image, PointF offset, Point imagePos, int angle)
        {
            // Credit: https://www.codeproject.com/Articles/58815/C-Image-PictureBox-Rotations

            if (image == null)
                return null;

            Bitmap rotatedBitmap = new Bitmap(image.Width, image.Height);
            rotatedBitmap.SetResolution(image.HorizontalResolution, image.VerticalResolution);

            using (Graphics G = Graphics.FromImage(rotatedBitmap))
            {
                G.TranslateTransform(offset.X, offset.Y);
                G.RotateTransform(angle);
                G.TranslateTransform(-offset.X, -offset.Y);
                G.DrawImage(image, new Point(imagePos.X, imagePos.Y));
            }

            return rotatedBitmap;

        }
        public void MakeBullet()
        {
            PictureBox bullet = new PictureBox();
            bullet.BackColor = Color.DarkGray;
            bullet.BorderStyle = BorderStyle.FixedSingle;
            bullet.Height = 5;
            bullet.Width = 10;

            bullet.Left = PbxPlayer.Left + PbxPlayer.Width;
            bullet.Top = PbxPlayer.Top + (PbxPlayer.Height / 2);

            bullet.Tag = "bullet";
            PnlGameBoard.Controls.Add(bullet);
        }

        public void RemoveBullet(PictureBox bullet)
        {
            Controls.Remove(bullet);
            bullet.Dispose();
        }

        /// <summary>
        /// Return true if 'pbx' PictureBox intersects with another PictureBox with the 'wall' tag
        /// </summary>
        /// <param name="pbx">The PictureBox we are checking for a collision</param>
        /// <returns></returns>
        public bool DetectCollision(PictureBox pbx, Point newPoint)
        {
            // Create a temporary PictureBox object to check for boundary collision without moving original pbx.
            PictureBox newpbx = new PictureBox();
            newpbx.Size = new Size(pbx.Width, pbx.Height);
            newpbx.Location = new Point(newPoint.X, newPoint.Y);
            foreach (var wall in Walls)
            {
                if (newpbx.Bounds.IntersectsWith(wall.Bounds))
                    return true;
            }
            // Release temporary object
            newpbx.Dispose();
            return false;
        }
   

    }

When I start the code, the image displays:

enter image description here

Then if I press "Q", which I've mapped to rotate the image anticlockwise:

enter image description here

It's the same if I try to rotate it clockwise.

What am I missing to make the rotated image appear in my PictureBox?

Lou
  • 2,200
  • 2
  • 33
  • 66

1 Answers1

1

The image disappears because you are drawing it at the PbxPlayer.Location that you pass to the RotateImage method.

if (RotateAnticlockwise == true)
{
    // ...
    PbxPlayer.Image = RotateImage(PbxPlayer.Image, offsets, PbxPlayer.Location, -2);
    // ...
}

if (RotateClockwise == true)
{
    // ...
    PbxPlayer.Image = RotateImage(PbxPlayer.Image, offsets, PbxPlayer.Location, 2);
    // ...
}

You are drawing the image outside the image bounds or the drawing canvas when you offset the location. The location must equal Point.Empty if you just want to rotate the image around the center.

public Bitmap RotateImage(Image image, PointF offset, int angle)
{
    if (image == null) return null;

    Bitmap rotatedBitmap = new Bitmap(image.Width, image.Height);
    rotatedBitmap.SetResolution(image.HorizontalResolution, image.VerticalResolution);

    using (Graphics G = Graphics.FromImage(rotatedBitmap))
    {
        G.TranslateTransform(offset.X, offset.Y);
        G.RotateTransform(angle);
        G.TranslateTransform(-offset.X, -offset.Y);
        G.DrawImage(image, Point.Empty);
    }

    return rotatedBitmap;
}

Speaking of the drawing canvas of the rotating image. It's PbxPlayer by the way not PnlGameBoard. So, implementing the Paint event of the latter doesn't make sense here because you are showing the rotation in the PictureBox and moving it in the timer's Tick event where you set PbxPlayer.Location = newPoint;. Hence, it doesn't seem likely that you want to draw on the PnlGameBoard surface.


Note, no need to keep creating images for each rotation angle this way. You just need the source image and move the RotateImage routine to the PictureBox.Paint event.

private readonly Image playerImage = Properties.Resources.SomeImage;
private int PlayerAngle = 0;
private bool RotateClockwise = true;

private void MainTimer_Tick(object sender, EventArgs e)
{
    PlayerAngle += RotateClockwise ? 2 : -2;
    PlayerAngle %= 360;
    PbxPlayer.Invalidate();
}

private void PbxPlayer_Paint(object sender, PaintEventArgs e)
{
    var g = e.Graphics;
    var src = (sender as Control).ClientRectangle;

    g.TranslateTransform(src.Width / 2, src.Height / 2);
    g.RotateTransform(PlayerAngle);
    g.TranslateTransform(-src.Width / 2, -src.Height / 2);
    g.DrawImage(playerImage, Point.Empty);
    g.ResetTransform();
}
dr.null
  • 4,032
  • 3
  • 9
  • 12