abitofcode

Simple interactive depth of field effect

Writing the Irregular touch detection post brought to mind another Lingo/Flash workaround that makes use of Photoshop. The effect is a depth of field that can be adjusted between the foreground and background without having to apply any sort of blurring effect in real time.

Fig 1. Select your image wisely, grass equals pain


Selecting an appropriate source image can make the process much simpler, the original image I took had too much joining the foreground to the background, deciding what should be in focus became a nightmare.

Fig 2. The source image we will work on, taken with a 3GS with nothing particularly in focus

Generating the images

The plan is to generate 2 images in Photoshop (or your graphics package of choice), a front image and a back image. The front image will have a lens blur applied that leaves an item in the from of the scene in sharp focus, in this case Ernie (the orange one). The back image will have a similar blur effect applied but will put an item in the background in sharp focus.

Generating the images can be done in any number of ways, for an image like the one I’m using, Photoshop’s lens blur tool makes experimenting quick and simple. I created 2 identical layers containing the source image, the top one being the foreground and the bottom one being the background. In a mask I then painted in the area of the foreground I wanted to stay sharp and applied a lens blur effect, I did the same for the background.

There’s a clear depth of field Photoshop tutorial here.

Fig 3. Foreground item in focus and Mask used with Photoshop lens blur effect

Fig 4. Background item in focus and Mask used with Photoshop lens blur effect

Working out what to make focused and what to leave blurred can take a little experimentation to get something looking right, there are areas of my background mask that can definitely be improved.
If you are rendering a 3D environment into a flat image these 2 images could be generated either by adjusting the focus in the 3D package, or rendering out the foreground and background items as separate layers and then blurring them individually.

Now we have the two images prepared we can see the effect within Photoshop before even writing a line of code. Altering the opacity of the top layer (the foreground) will blend the two layers. Zero opacity will show the background layer only which has the effect of showing the rear item in focus, 100% opacity for the top layer will show the top layer only which results in the foreground item being in focus and any value in between will show varying amounts of both layers.

The approach avoids running realtime blur effects but it still involves compositing transparency so it’s not something I would typically use within a game loop (especially not at this size). I did use it once in flash to simulate a rolling fog bank, I just took the same long thin fog texture placed it over itself, reversed it horizontally and then randomly faded the top layers alpha up and down.

A Cocos2d example

To simplify the example this is not a best practice, and once again I’ve shoe horned everything into the HelloWorldLayer that comes with the cocos2d template so it should be relatively familiar. The source code and any assets used in this post can be downloaded at the bottom of the page.

The init function simply adds the 2 images over each other, with the background image being set at z = 0, and the foreground image above it at z = 1. To make this a little more interesting I’ve added some touch methods that take the touch and use the x position to calculate the opacity. As you move your finger from Ernie to Bert the opacity adjusts resulting in the focus being roughly where your finger is.

-(void)setFocus:(CGPoint) pt
{
    // get a reference to the front image
    CCSprite *front_image = (CCSprite*)[self getChildByTag:FRONT_IMAGE];
 
    // convert the UIKit coordinate  to an openGl coordinate
    // world space (origin in lower left corner)
    pt=[[CCDirector sharedDirector] convertToGL:pt];
 
    // node space - relative to front_image. Without 
    // this we would get the coordinate relative to the layer 
    // not the front_image image. As the Front_image is offset
    // by 100 pixels to the right and 50 pixels up the
    // hit areas would not be correct
    pt = [front_image convertToNodeSpace:pt];
 
    // is the touch within the bounds of the image
    if(CGRectContainsPoint([front_image boundingBox], pt)) 
    {
        // If the point is to the left of ernies face set the front_image
        // that contains the sharp version of Ernies face to opacity 
        // 255 (opaque) 
        if(pt.x < 280) {
            front_image.opacity = 255;
        } else if(pt.x > 540) {
            // If the point is to the right of Berts body set the front_image
            // that contains the sharp version of Ernies face to opacity 
            // 0 (transparent)
            front_image.opacity = 0;
        } else {
            // between these values calculate an opacity that fades out the 
            // sharp Ernie image as the touch point approaches Bert
            // We have 255 steps (0-255 opacity) over 260 pixels (540-280)
            float stepIncrement = 255.0f /260.0f;
 
            float fOpacity = (pt.x - 280) * stepIncrement;
 
            // we need to go from opaque to transparent hence 
            // the adjustment
            front_image.opacity = 255 - (GLubyte)fOpacity;
        }
        //NSLog(@"Point: %@", NSStringFromCGPoint(pt));            
    }   
}
-(void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    // get a touch
    UITouch *touch = [touches anyObject];
 
    // get a touch in the UIKit coordinate system
    CGPoint loc=[touch locationInView:[touch view]];
 
    // Take the point and set the opacity of the top image
    [self setFocus:loc];    
}
 
-(void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{    
    // get a touch
    UITouch *touch = [touches anyObject];
 
    // get a touch in the UIKit coordinate system
    CGPoint loc=[touch locationInView:[touch view]];
 
    // Take the point and set the opacity of the top image    
    [self setFocus:loc];
}

The result is subtle but works nicely with touch control.
edit: According to the wife the effect is not subtle and I need my eyes tested

Fig 5. I've added a couple more images to the download to play with

Download the files here: [download id=”5″ format=”2″]

4 Comments

  1. Nice work, I can’ wait to play around with it!

  2. I know nothing of Cocos2d (I am a graphic designer more inclined towards drawing and cartooning – but I work on Photoshop and Flash,) but I really enjoyed your tutorial. I found myself opening an image and trying it out :)

  3. Very useful for game story scenes.

  4. @Harvey, Nifty, Jose Thanks for the feedback :)

    I’m surprised how much better it feels with the touch control. In the past I’ve controlled the depth with a slider or used the mouse position but those approaches feel disjointed in comparison.