tags:

views:

61

answers:

1

I'm trying to implement a simple "zoom to point" functionality in WinForms. When the mouse is at the same point and the mouse wheel is scrolled in/out, it works great. When the mouse position is changed between scrolls, it jumps to that position and is very wonky. Here's the code for the control that you can add to a Form for testing:

public class Canvas : Control
{

    private Bitmap Image;
    private TextureBrush ImageBrush;
    private Point Origin;
    private Matrix TransformMatrix;

    public float ZoomScale
    {
        get;
        set;
    }

    public Canvas()
    {
        this.SetStyle(ControlStyles.ResizeRedraw | ControlStyles.OptimizedDoubleBuffer, true);

        this.Image = // Load your picture here.
        this.ImageBrush = new TextureBrush(this.Image, WrapMode.Clamp);
        this.ZoomScale = 1.0f;

        this.TransformMatrix = new Matrix();

    }

    protected override void OnPaint(PaintEventArgs e)
    {
        float zs = this.ZoomScale;

        var matrix = this.TransformMatrix.Clone();
        e.Graphics.Transform = matrix;

        var c = e.ClipRectangle;

        // Transform the clip rectangle, is this even right?
        var x = (int)Math.Round(c.X / zs - matrix.OffsetX / zs);
        var y = (int)Math.Round(c.Y / zs - matrix.OffsetY / zs);
        var w = (int)Math.Round(c.Width / zs);
        var h = (int)Math.Round(c.Height / zs);

        // Draw the image scaled and translated.
        e.Graphics.FillRectangle(this.ImageBrush, x, y, w - 1, h - 1);

    }

    protected override void OnMouseWheel(MouseEventArgs e)
    {
        if (e.Delta > 0)
        {
            this.ZoomScale += 0.1f;
        }
        else
        {
            this.ZoomScale -= 0.1f;
        }

        this.ZoomToPoint(e.Location);
        this.Refresh();
    }

    private void ZoomToPoint(Point origin)
    {
        this.Origin = origin;
        float zoomScale = this.ZoomScale;

        // The important part.
        var matrix = new Matrix(1, 0, 0, 1, 0, 0);
        matrix.Reset();
        matrix.Translate(-origin.X, -origin.Y, MatrixOrder.Append);
        matrix.Scale(zoomScale, zoomScale, MatrixOrder.Append);
        matrix.Translate(origin.X, origin.Y, MatrixOrder.Append);
        this.TransformMatrix = matrix;
    }

    protected override void OnResize(EventArgs e)
    {
        base.OnResize(e);
        this.ZoomToPoint(this.Origin);
    }

}

So the important part is obviously the affine transformation. I know it's in Adobe Flex, but I got the idea of Translate, Scale, Translate from this website (great example of what I'm trying to do). I didn't realize that MatrixOrder.Append was the key in .NET until I tried. But the problem still remains. Try loading the control in a WinForms application and you'll see what I mean. Does anyone know what the issue is with this?

EDIT: The reason I need to manually calculate the rectangle is because I'm going to be drawing more than just an image. It's a canvas that performs like Visio. I'll also need a method for transforming a specific Point.

EDIT 2: I understand that I can use Invert() on the Matrix then TransformPoints() to get a proper transformed point but it still doesn't solve the problem of the wonky mouse movement. I was thinking of calculating the Origin from the location of the top corner of the image but it didn't work.

+1  A: 

Ah-ha! I found it! After analyzing the link that I posted, I realized "Hey, he's not setting a "zoom-scale" anywhere. Then I found the problem. I was creating a new Matrix every time. This isn't the right way to do it. It should scale/translate the current transformation:

private void ZoomToPoint(float scale, Point origin)
{
    var matrix = this.TransformMatrix;
    matrix.Translate(-origin.X, -origin.Y, MatrixOrder.Append);
    matrix.Scale(scale, scale, MatrixOrder.Append);
    matrix.Translate(origin.X, origin.Y, MatrixOrder.Append);

    this.TransformMatrix = matrix;
}

And the call to that would be like:

// Zoom in
this.ZoomToPoint(6 / 5.0f, e.Location);

and

// Zoom out
this.ZoomToPoint(5 / 6.0f, e.Location);
TheCloudlessSky
Ah bummer, I was hoping to solve this one. :)
Carter
@Carter - Yeah as soon as I saw it I had a "D'OH!" moment.
TheCloudlessSky