Mandelbrot Zoomer in WPF
Written by Mike James   
Thursday, 23 July 2015
Article Index
Mandelbrot Zoomer in WPF
Plotting
Zooming
Where Next

The zooming

At this point we have the necessary code to allow the user to plot a default view of the Mandelbrot set.

Next we want to let the user select an area by dragging with a mouse and then recomputing the Mandelbrot set so that this selected area is blown up to cover the entire picture box.

To allow the user to mark out an area we need to make use of the mouse events – mousedown, mousemove and mouseup.

Essentially what we need to do is record the x,y co-ordinates when the user presses the mouse button down. This marks the first corner of the rectangle. Then as the user moves the mouse we animate a rectangle drawn from the first corner to the current mouse position. Finally the user lets go of the mouse button , i.e. a mouseup event occurs, and we store the final mouse co-ordinates of the rectangle.

This sounds easy but as we draw a rectangle by specifying its height, width and top lefthand corner things are a little more tricky. In particular the mouse down position is only the top lefthand corner of the rectangle if the user drags down and to the right. If they drag up and to the left it is the bottom right hand corner.

The solution to this problem is to sort the mouse positions to find the top lefthand corner no matter what the two points specified are.


XAML

First we might as well create the selection rectangle ready to be used:

private Rectangle selection = new Rectangle()
   {Stroke = Brushes.Black,
    StrokeThickness = 1,
    Visibility = Visibility.Collapsed };

You can define this Rectangle using XAML is you want but using object initialization syntax code is just as easy.

We also have to add the Rectangle to the Canvas and the best place to do this is in the constructor:

Canvas1.Children.Add(selection);

We also need a flag to signal that a selection is in progress and a Point to store the location the mouse button is first pressed down:

private bool mousedown = false;
private Point mousedownpos;

The easiest way to add the event handlers we need is to move to the XAML defining the MainWindow select the Canvas and then select the event icon in the properties window. Then scroll down until you find MouseLeftButtonDown - double click in the textbox and an event handler will be added to the XAML and to the C# code. Repeat the operation for the Mousemove event and the MouseLeftButtonUp event.

The mousedown event handler is:

private void Canvas1_MouseLeftButtonDown(
      object sender,
      MouseButtonEventArgs e)
{
 mousedown = true;
 mousedownpos = e.GetPosition(Canvas1);
 Canvas.SetLeft(selection,mousedownpos.X);
 Canvas.SetTop(selection,mousedownpos.Y);
 selection.Width = 0;
 selection.Height = 0;
 selection.Visibility=Visibility.Visible;
}

This is an easy method but there are some subtle points.

The first is that the Canvas only receives routed events from image1. We also get the mouse position relative to Canvas1. Then we set the top left hand corner of the selection rectangle to the mouse down position and make it visible.

The MouseMove event handler is only complicated because of the need to identify the top left-hand corner of the rectangle between the two points. 

First we have to check that the mousedown event occured - we don't do anything if the mouse is just moving over the Canvas:

private void Canvas1_MouseMove(
                  object sender,
                  MouseEventArgs e)
{
 if (mousedown)
 {

If the mouse is moving after a down button event then we can retrieve its position and work out the difference between its current position and where the mousedown event occurred: 

Point mousepos = e.GetPosition(Canvas1);
Vector diff =  mousepos-mousedownpos;

We might as well start with the assumption the mousedownpos is the TopLeft hand corner - after all most users drag down and to the right:

Point TopLeft = mousedownpos;

Now we test to see if any of the differences were negative and if so we have to swap a co-ordinate:

if (diff.X < 0)
{
 TopLeft.X = mousepos.X;
 diff.X = -diff.X;
}
if (diff.Y < 0)
{
 TopLeft.Y = mousepos.Y;
 diff.Y = -diff.Y;
}

Notice that now TopLeft really does hold the position of the top left of the rectangle. This, together with the differences, can be used to set the position of the selection:

 selection.Width = diff.X;
 selection.Height = diff.Y;
 Canvas.SetLeft(selection, TopLeft.X);
 Canvas.SetTop(selection, TopLeft.Y);
 }
}

 

 

Banner

 

All that remains is the mouseup event handler and this is comparatively easy. It simply has to cancel the selection operation, make the rectangle invisible again and compute the new view of the Mandelbrot set using the new Rectangle:

private void Canvas1_MouseLeftButtonUp(
          object sender,
          MouseButtonEventArgs e)
{
 mousedown = false;
 selection.Visibility=Visibility.Collapsed;

To compute the new view of the Mandelbrot set we first need to convert the bitmap position as specified by selection into a Rect in terms of x,y co-ordinates.

This is just the same calculation we have already performed in converting px,py to x,y in the drawSet method:

double xscale = (area.Right-area.Left)/
                              image1.Width;
double yscale = (area.Top-area.Bottom)/
                              image1.Height;
Point TopLeft= new Point(area.Left +
  Canvas.GetLeft(selection) * xscale,
  area.Top-Canvas.GetTop(selection)*yscale);
Point BottomRight = TopLeft+new Vector(
      selection.Width*xscale,
          -selection.Height*yscale);  
area=new Rect(TopLeft,BottomRight);

The only clever parts are the use of a Vector displacement to compute the bottom right corner rather than converting its co-ordinates from scratch.

Finally we can compute the new view:

 image1.Source = drawSet(area);
}

Now the user can zoom in on any area of the set. Notice that because WPF is a persistent vector graphic system we don't have to worry about drawing and erasing the rectangle that indicates the selected area - the system takes care of it. All we have to do is set its position and size.

 

Banner

 

<ASIN:0470477229>

<ASIN:1430246839>

<ASIN:0321374479>



Last Updated ( Thursday, 23 July 2015 )