3

I am trying to develop a metronome application which I have almost completed.

The only portion I have left is a secondary visual that requires me to rotate an image of an arm (think minute hand on a clock) backwards and forwards in time.

how can I rotate an image and do so by not rotating it around its center point. in the scenario I am writing for I need to be able to rotate the image around a point in the middle of its bottom border.

Also, I would like to be able to rotate the image from X to Y in n milliseconds and have it ease in and out in the animation. Is this a bridge too far for C# and are there any libraries that could help me achieve this advance type of physics animation.

many thanks

Dan

  • Are you able to rotate via the center point now? If so, you could just double the image and only show the top half. – Austin Salonen Jun 08 '12 at 18:34
  • You might try using XNA. I've done this in winforms by inheriting panel and filling it with an XNA device. XNA gives you great 2D sprite manipulation - fast and smooth! http://www.riemers.net/eng/Tutorials/XNA/Csharp/Series2D/Rotation.php – J... Jun 08 '12 at 18:38

3 Answers3

2

Not what you asked for, but:

Instead of doing a processor-intensive operation many times per second, you should consider flipping through a set of static images instead.

egrunin
  • 24,650
  • 8
  • 50
  • 93
2

Expanded Reply

There is no flicker whatsoever. The SetStyle in the ctor takes care of that. What you see as "flicker" is an artifact cause by three factors:

  1. The update rate is only 10/sec. Try increasing that to 20 or 30.
  2. The direction value is course. It should be based on time/accelleration. That is an exercise left to you.
  3. The crappy "hand" image has hard, aliased edges. No matter how fast you update or how smooth you animate it, it's going to look jittery. Again, doing anti-aliased, blended graphics processing is left as an exercise.

Look at the code more carefully. This is not "painted on the form", it's a custom control. See how MetronomeControl derives from Control? See how we created the form by adding a MetronomeControl to it?

Picture boxes are for displaying static images, not for custom controls!

The way it updates is by creating a timer. When the timer's Tick event fires we update the angle and direction, more generally, we update the state of the control. The call to Invalidate tells the Operating System, "Hey, I need to be repainted, send me a WM_PAINT message when it's convenient!". Our OnPaint override simply paints the current state of the control.

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;

class MetronomeControl : Control
{
    private Bitmap hand;
    private float angle = 0;
    private float direction = 2;
    private Timer timer = new Timer { Enabled = true, Interval = 30 };

    public MetronomeControl()
    {
        SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint | ControlStyles.Opaque | ControlStyles.AllPaintingInWmPaint, true);
        hand = CreateCrappyHandBitmap();
        timer.Tick += new EventHandler(timer_Tick);
    }

    void timer_Tick(object sender, EventArgs e)
    {
        if (angle < -45 || angle > 45)
            direction = -direction;
        angle += direction;
        Invalidate();
    }

    private static Bitmap CreateCrappyHandBitmap()
    {
        Bitmap bitmap = new Bitmap(100, 300, PixelFormat.Format32bppArgb);
        using (Graphics graphics = Graphics.FromImage(bitmap))
        {
            graphics.Clear(Color.Transparent);
            graphics.FillRectangle(Brushes.LightGray, 50 - 5, 0, 10, 300);
            graphics.FillPolygon(Brushes.LightSlateGray, new Point[] { new Point(50 - 30, 40), new Point(50 + 30, 40), new Point(50 + 20, 80), new Point(50 - 20, 80) });
            graphics.FillEllipse(Brushes.LightSlateGray, 0, 200, 100, 100);
        }
        return bitmap;
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        // Erase background since we specified AllPaintingInWmPaint
        e.Graphics.Clear(Color.AliceBlue);

        e.Graphics.DrawString(Text, Font, Brushes.Black, new RectangleF(0, 0, ClientSize.Width, ClientSize.Height), new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center });

        // Move the 0,0 point to the just below the bottom-center of our client area
        e.Graphics.TranslateTransform(ClientSize.Width / 2, ClientSize.Height + 40);
        // Rotate around this new 0,0
        e.Graphics.RotateTransform(angle);
        // Turn on AA to make it a bit less jagged looking
        e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
        // Draw the image so that the center of the ellipse is at 0,0
        e.Graphics.DrawImage(hand, 0 - hand.Width / 2, 0 - hand.Height + 50);

        // Reset the transform for other drawing
        e.Graphics.ResetTransform();

        base.OnPaint(e);
    }
}

static class Program
{
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form
        {
            Text = "Metronome Control Demo",
            ClientSize = new Size(640, 480),
            Controls =
            {
                new MetronomeControl
                {
                    Location = new Point(10, 10),
                    Size = new Size (340, 300),
                    Font = new Font(FontFamily.GenericSansSerif, 24),
                    Text = "Metronome Control Demo",
                }
            }
        });
    }
}
Tergiver
  • 14,171
  • 3
  • 41
  • 68
  • that is truly amazing as an answer, i will accept this, my only question is how can i stop it flickering (its not terrible but noticable) is this just a facet of winforms timer based animation ? –  Jun 09 '12 at 10:06
  • could you do me a huge favour and show me how to use your code inside a picture box on a form instead of on the form itself. I got confused as you are calling Invalidate and getting the form to repaint.... thanks in advance –  Jun 09 '12 at 13:25
  • @DanielGwalter I've edited the answer to reply to your questions. – Tergiver Jun 09 '12 at 15:03
  • Try if you can speed up drawing of your control by setting `SetStyle(ControlStyles.OptimizedDoubleBuffer, true);`. In one of my owner drawn controls I got a dramatic boost by calling this in the controls constructor. – Olivier Jacot-Descombes Jun 09 '12 at 15:23
  • @OlivierJacot-Descombes As you can see, that style already exists (it has from the very beginning). – Tergiver Jun 09 '12 at 16:21
  • ty your expanded reply rocks... the reason i was askin is that I have a needle image (png) and an overlay png (glass window) ideally i was looking to render the needle behind the transparency of the window png so it looks like a dashboard speedometer ticking in time.... –  Jun 09 '12 at 19:12
0

may be this sample code is useful:

Graphics x=Graphics.FromImage(m);
x.TranslateTransform(m.Width / 2, m.Height / 2);
x.RotateTransform(30);
SizeF textSize = x.MeasureString("hi", font);
x.DrawString("hi", font, Brushes.Red, -(textSize.Width / 2), -(textSize.Height / 2);