Click here to Skip to main content
Rate this: bad
good
Please Sign up or sign in to vote.
See more: C# GDI+ WinForm
I'm back. I've got an image that I'm trying to scale, rotate, and move within a user control. The scaling and rotate are working fine, but when I try to move AFTER scaling or rotating, it doesn't quite move like it ought to, and the more it's scaled or rotated, the worse it gets. Here's the code so you can see what I mean.
 
EDIT (Oct 10) =============================
By "not like it ought to", I mean when I move the mouse, the image moves diagonally to the direction of the mouse cursor movement on the screen. Through some test coding that draws the origin's axis on the screen, I found that the axis is adopting the angle of the rotation, so if you rotate the image 45 degrees, the axis is titled at 45 degrees. I suppose this is why the image moves at an angle, because relative to the axis, the mouse pointer is moving diagonally, when visually, it (the cursor) is moving veritcally or horizontally. I see trigonometry in my future...
 
For testing, I draw a crosshair at the origin point of the image rectangle. It rotates with the image. When I go to move the image, the hit test shows that the rectange it's finding is not the rotated one, nor even in the rotated area. It's quite the mess.
 
My *aim* is to rotate the image based on the center point of the image, and only be able to move it when the mouse cursor is positioned over the actual rotated rectangle. I want to do all this without any flicker, and without having to create a new bitmnap.
===========================================
 
I've tried a number of things (using regions, finding the center of the rotate rectangle) to get this to act right, and i just can't get my head around it.
 
EDIT ======================================
Updated source code - The best way to see what it's doing is to actually put this control into a test app, and run it.
===========================================
 
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Windows.Forms;
 
namespace BitmapTest
{
    public partial class CPCanvas2 : UserControl
    {
        private System.ComponentModel.IContainer components = null;
 
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }
 
        private void InitializeComponent()
        {
            this.SuspendLayout();
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.Margin = new System.Windows.Forms.Padding(0);
            this.Name = "Canvas2";
            this.Size = new System.Drawing.Size(640, 480);
            this.ResumeLayout(false);
        }
 
        private bool      m_moving  = false;
        private Size      m_originalSize = new Size(0,0);
        public  Point     m_origin  = new Point(0, 0);
        private Point     m_offset  = new Point(0, 0);
        private Image     m_weapon  = null;
        public  Rectangle m_bmpRect = new Rectangle(0,0,0,0);
        public  float     m_scale   = 1f;
        public  float     m_angle   = 0f;
 
        public CPCanvas2() : this(null, null) { }
        public CPCanvas2(Image imagePicture) : this(imagePicture, null) { }
        public CPCanvas2(Image imageBackground, Image imagePicture)
        {
            SetStyle(ControlStyles.OptimizedDoubleBuffer | 
                     ControlStyles.AllPaintingInWmPaint, true);
            Dock                  = DockStyle.Top | DockStyle.Left;
            BackgroundImageLayout = ImageLayout.Stretch;
            BackgroundImage       = imageBackground;
            m_weapon              = imagePicture;
            m_bmpRect.Width       = (int)(m_weapon.Width * m_scale);
            m_bmpRect.Height      = (int)(m_weapon.Height * m_scale);
            m_originalSize        = m_bmpRect.Size;
 
            m_scale = 1f; // set default scale
            InitializeComponent();
        }
 
        //------------------------------------------------------------------------
        protected override void OnMouseDown(MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                m_moving = true;
            }
            base.OnMouseDown(e);
        }
 
        //-------------------------------------------------------------------------
        protected override void OnMouseUp(MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                m_moving = false;
            }
            base.OnMouseUp(e);
        }
 
        //------------------------------------------------------------------------
        protected override void OnPaint(PaintEventArgs e)
        {
            if (m_weapon != null)
            {
		float x = ((m_bmpRect.Width * m_scale) / 2f);
		float y = ((m_bmpRect.Height * m_scale) / 2f);
		e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
		e.Graphics.TranslateTransform(-x, -y, MatrixOrder.Append);
		e.Graphics.RotateTransform(m_angle, MatrixOrder.Append);
		e.Graphics.ScaleTransform(m_scale, m_scale, MatrixOrder.Append);
		e.Graphics.TranslateTransform(x, y, MatrixOrder.Append);
                e.Graphics.DrawImage(m_weapon, m_origin);
            }
            base.OnPaint(e);
        }
 
	//------------------------------------------------------------------------
        protected override void OnMouseMove(MouseEventArgs e)
        {
            if (m_moving)
            {
                m_origin.X = e.Location.X - m_offset.X;
                m_origin.Y = e.Location.Y - m_offset.Y;
                m_bmpRect.X = m_origin.X;
                m_bmpRect.Y = m_origin.Y;
                Refresh();
            }
            else
            {
                m_offset.X = e.Location.X - m_bmpRect.X;
                m_offset.Y = e.Location.Y - m_bmpRect.Y;
            }
            base.OnMouseMove(e);
        }
 
        //------------------------------------------------------------------------
        public void SetScale(float scale)
        {
            if (scale > 0.0f)
            {
                m_scale = scale * 0.01f;
                m_bmpRect.Width  = (int)(m_weapon.Width * m_scale);
                m_bmpRect.Height = (int)(m_weapon.Height * m_scale);
                Refresh();
            }
        }
 
        //-------------------------------------------------------------------------
        public void SetAngle(float angle)
        {
            m_angle = angle;
            Refresh();
        }
    }
 
}
Posted 9-Oct-11 11:02am
Edited 10-Oct-11 5:18am
v6
Comments
BillWoodruff at 9-Oct-11 18:15pm
   
"when I try to move AFTER scaling or rotating, it doesn't quite move like it ought to, and the more it's scaled or rotated, the worse it gets."
 
Can you describe more specifically what you observe ? Jittering ... stuttering ... flashing ... degradation of image visual quality ?
   
See my edit block at the top of the question.
johannesnestler at 10-Oct-11 10:02am
   
Ah John, Now I found you second question :-) Look at the solution I created for you (on your first question). You still use the image dimensions for your moving calculations, in my example I recalculate the SCALED image dimensions, I think this is another problem...
Rate this: bad
good
Please Sign up or sign in to vote.

Solution 1

GDI+ is using matrices internally to sort out your transformations. So you need to be careful which order you do them in, for example if you had a sphere which you moved and then rotated it would be like the earth moving around the sun, if you rotated then moved it would be like the earth staying still but spinning around it's own axis.
 
So, if you want to rotate the image around some arbitrary point, you will need to shift the picture so that your rotation point is at (0,0) [which should by default be the centre of the image] then rotate by the desired amount, and then move it again to where you need it to be.
 
If your image was 100x100 for example the default centre would be (50,50) if you wanted to rotate it around (75,75) then move the picture 250 pixels to the right you would have to do this:
 
e.Graphics.TranslateTransform(-25,-25); //So that the point at (75,75) is now where (50,50) used to be
e.Graphics.RotateTransform(45);
e.Graphics.TranslateTransform(250 +25,25); //Remember to add back the translation you did before

e.Graphics.ScaleTransform(0.25f, 0.25f); //Scale last
e.Graphics.DrawImage( myImage, 0, 0 ); //Draw at 0,0, use the last TranslateTransform to position the image, otherwise the location of the image will depend on the ScaleTransform
 
EDIT: Even easier that translating then rotating, I've just noticed that you could use e.Graphics.Transform.RotateAt(PointF point, float angle) which means you could skip the first translate and you won't have to compensate for it later.
 
EDIT: As for checking if the mouse cursor is within the rotated rectangle you're going to need to do a little more work. All you need to do is move the mouse position into the rectangles object space, so if you rotated the image by 45 degrees then you need to rotate the mouse position by -45 degrees around the same point. Even easier, you're using a matrix already so when you change the matrix to render your image, store it so that you can use is later. Then your check can become something like:
 
Matrix InverseMat;
 
void OnPaint(...)
{
 // Transform and draw your image
 ...
 InverseMat = e.Graphics.Transform;
 InverseMat.Inverse();
 ...
}
 
bool RectContainsPoint(Rectangle rect, Point MousePos)
{
  Point[] pts = { MousePos };
  InverseMat.TransformPoints(pts);
 
  return rect.Contains(pts[0]);
}
  Permalink  
v3
Comments
John Simmons / outlaw programmer at 10-Oct-11 10:11am
   
Yeah, I've been playing with that, but the weirdness prevails.
John Simmons / outlaw programmer at 10-Oct-11 10:31am
   
Using e.Graphics.Transform doesn't rotate the image... :(
John Simmons / outlaw programmer at 10-Oct-11 10:44am
   
Here's what I have now:
 
float x = m_origin.X + ((m_bmpRect.Width * m_scale) / 2f);
float y = m_origin.Y + ((m_bmpRect.Height * m_scale) / 2f);
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
e.Graphics.TranslateTransform(-x, -y, MatrixOrder.Append);
e.Graphics.RotateTransform(m_angle, MatrixOrder.Append);
e.Graphics.ScaleTransform(m_scale, m_scale, MatrixOrder.Append);
e.Graphics.TranslateTransform(x, y, MatrixOrder.Append);
e.Graphics.DrawImage(m_weapon, m_origin);
e.Graphics.ResetTransform();
 
Movement is now correct (it goes where the mouse pointer goes and at the same speed). The only remaining problem is that when I scale the image, the lower the scale, the closer the rotation point moves to the top/left corner of the image. If I scale it back up to 100% size, the rotation point returns to the center...
Rate this: bad
good
Please Sign up or sign in to vote.

Solution 2

Hi John,
 
after the RotateTransform and ScaleTransform call the graphics context will apply the appropriate transformations before drawing anything. Since you specified the point where to draw along with the DrawImage method, this is the point the image will be rotated around. To do the moving you should use the TransformTranslate method of graphics context.
 
        protected override void OnPaint(PaintEventArgs e)
        {
            if (m_weapon != null)
            {
                float dx = 1.0 * m_origin.X;
                float dy = 1.0 * m_origin.Y;
                e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
                e.Graphics.RotateTransform(m_angle);
                e.Graphics.ScaleTransform(m_scale, m_scale);
                e.Graphics.TranslateTransform(dx, dy); // <== Use the TranslateTransform
                e.Graphics.DrawImage(m_weapon, imagesCenterPoint);
            }
            base.OnPaint(e);
        }
 
The variable imagesCenterPoint should be image_width/2 and image_height/2. The order of the transformations should be OK as it is. This setup will rotate the image around imageCenterPoint for m_angle it will then be scaled and then drawn on the graphics context with the offset dx/dy which was specified with the TranslateTransform.
 
Best Regards,
 
—MRB
  Permalink  
v2
Comments
SK Genius at 10-Oct-11 10:02am
   
You should translate and then scale, otherwise the translation will be scaled as well.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

  Print Answers RSS
0 OriginalGriff 277
1 Maciej Los 210
2 BillWoodruff 205
3 Jochen Arndt 180
4 DamithSL 165
0 OriginalGriff 5,130
1 DamithSL 4,157
2 Maciej Los 3,670
3 Kornfeld Eliyahu Peter 3,470
4 Sergey Alexandrovich Kryukov 2,821


Advertise | Privacy | Mobile
Web04 | 2.8.141216.1 | Last Updated 10 Oct 2011
Copyright © CodeProject, 1999-2014
All Rights Reserved. Terms of Service
Layout: fixed | fluid

CodeProject, 503-250 Ferrand Drive Toronto Ontario, M3C 3G8 Canada +1 416-849-8900 x 100