2.1 Selecting a part of a static image

by Marko Riedel

2.1.1 Idea

This example is taken from a fractal viewer, where the fractal is stored in an image and the user wishes to select a circular region of the image for the purpose of zooming. It is not difficult to alter this code to select, say, a rectangular region.

The structure of the routine is simple. We override NSView’s mouseDown: method and process mouse-dragged events until we get a mouse-up event. We update the selection every time there is a mouse-dragged event.

2.1.2 Implementation

We must store two points, namely the start point, and the current point. The start point tells us where the center of the selection lies, and the larger of the two distances (horizontal, vertical) from the current point to the start point defines the radius of the circle being selected.

 
#define PAD 5 
 
- (void)mouseDown:(NSEvent *)theEvent 
{ 
    NSPoint startp, curp; 
    NSEvent *curEvent = theEvent; 
 
    float dx, dy, prevr=0, r; 
    NSRect update; 
 
    double deltare, deltaim, 
        newminre, newmaxre, newminim, newmaxim; 
 
    startp = [curEvent locationInWindow]; 
    startp = [self convertPoint:startp fromView: nil];

Both the start and the current point are supposed to be in view coordinates. Hence we must convert them from screen coordinates. We set the previous radius to zero, prepare the start point and are now ready to enter the loop.

 
    do { 
        curp = [curEvent locationInWindow]; 
        curp = [self convertPoint:curp fromView: nil]; 
 
        dx = curp.x-startp.x; 
        dy = curp.y-startp.y; 
 
        if(dx<0){ 
            dx = -dx; 
        } 
        if(dy<0){ 
            dy = -dy; 
        } 
 
        r = (dx<dy ? dx : dy);

The first step is to convert the location of the drag event into the view’s coordinates. We compute the radius next. We must restore the pixels that we altered when we drew the circle in the previous iteration of the loop, and draw the circle for the current radius. So we compute a square whose center lies at the start point and which includes the circle that was drawn. We copy the data from the image over this rectangle, thereby restoring the pixels that we altered.

 
        [self lockFocus]; 
 
        if(prevr){ 
            update=NSMakeRect(startp.x-prevr-PAD, startp.y-prevr-PAD, 
                               2*(prevr+PAD), 2*(prevr+PAD)); 
            update=NSIntersectionRect(update, [self frame]); 
 
            [image compositeToPoint:update.origin 
                    fromRect:update 
                    operation:NSCompositeCopy]; 
        }

The next step is to draw a circle centered at the start point and having the new radius. This code actually draws two circles, one black, one white and having a slightly larger radius. This assures that the user sees the circle even if it overlaps with a monochrome (white, black) portion of the image.

 
        PSsetgray(0.0); 
        PSarc(startp.x, startp.y, r, 0, 360); 
        PSstroke(); 
        PSsetgray(1.0); 
        PSarc(startp.x, startp.y, r+1, 0, 360); 
        PSstroke(); 
 
        [self unlockFocus]; 
        [[self window] flushWindow]; 
 
        prevr = r;

The last step is to flush the data and remember the new radius so that the underlying area can be restored. We process events until we get a mouse-up.

 
        curEvent = 
            [[self window] 
                nextEventMatchingMask: 
                     NSLeftMouseUpMask  NSLeftMouseDraggedMask]; 
    } while([curEvent type] != NSLeftMouseUp); 
 
 
    deltare = maxre-minre; 
    deltaim = maxim-minim; 
    newminre = deltare*(double)(startp.x-r)/(double)res+minre; 
    newmaxre = deltare*(double)(startp.x+r)/(double)res+minre; 
    newminim = deltaim*(double)(startp.y-r)/(double)res+minim; 
    newmaxim = deltaim*(double)(startp.y+r)/(double)res+minim; 
 
    minre=newminre; 
    maxre=newmaxre; 
    minim=newminim; 
    maxim=newmaxim; 
 
    [self update]; 
    [self setNeedsDisplay:YES]; 
}

We invoke setNeedsDisplay: so that the selected portion of the image will be displayed after it has been computed by update. We have to compute the coordinates for this to work. Assume that we are working with four values that describe a portion of the complex plane, i.e. real min and max and complex min and max; the variable res contains the width and height of the view. We convert the coordinates of the square centered at the start point into the coordinate system of the unit square, then convert into the coordinates of the square being displayed, which gives us the new square.