Morphing Depth of Field
Two (or more) photographs of the same scene and that have different focal distances can be joined together to create an image where both distant and close objects can be in focus.
Quoting from A Multifocus Method for Controlling Depth of Field by Paul Haeberli:
When a photograph is taken with a camera, the lens is focused at a particular distance. Objects nearer or farther than this focal distance will appear blurred. By changing the focus of the lens, near objects or distant objects can be made to appear in sharp focus. If you want to create an image where distant objects as well as close objects are in focus, two or more images can be merged together to make an image with increased depth of field…
Instead of creating a single, in-focus image from two images, I wanted to see how a smooth morphing effect would look like.
Hovering over the image controls how much each of the two images contributes to the final image. Vertical movement controls the contribution amount of the image where the bottle at the front in-focus. Horizontal movement controls the image where the bottle at the back is in-focus.
To get a natural, focus-back-and-forth experience, move the mouse from the top right corner to the bottom left corner and back.
This Java applet was done using Processing. The source is below. You can also download the PDE file.
/*
Depth of Field
by Ates Goral
Nov 6, 2003
Based on
"A Multifocus Method for Controlling Depth of Field"
by Paul Haeberli
http://www.sgi.com/grafica/depth/index.html
The images that are used are taken from the original
document.
Instructions:
- Roll over the photograph to bring the near and far
portions in and out of focus.
- Vertical movement controls the near portion.
- Horizontal movement controls the far portion.
*/
// Globals
PImage imgNear, imgFar, imgSel, imgTxtNear, imgTxtFar;
boolean bInitial; // Initial paint?
void setup()
{
size(250, 250);
cursor(CROSS);
imgNear = loadImage("near_in_focus.gif");
imgFar = loadImage("far_in_focus.gif");
imgSel = loadImage("selector.gif"); // Pixel selector
imgTxtFar = loadImage("txt_far.gif");
imgTxtNear = loadImage("txt_near.gif");
// Thin frame around the photograph
stroke(0);
noFill();
rect(0, 0, 240, 240);
// Small decorative rectangle at bottom right
noStroke();
fill(0);
rect(241, 241, 9, 9);
bInitial = true; // Enforce initial paint
}
void loop()
{
// Don't bother if the mouse hasn't moved
if (pmouseX == mouseX && pmouseY == mouseY && !bInitial)
return;
float nearFade, farFade;
if (!bInitial) // If not doing the initial paint, use
// mouse input
{
nearFade = mouseY / 239.0;
farFade = mouseX / 239.0;
}
else // If doing the inital paint, assume 50% fade
{
nearFade = 0.5;
farFade = 0.5;
bInitial = false;
}
for (int y = 0; y < 239; y++)
{
int offY = y * 239;
for (int x = 0; x < 239; x++)
{
int offXY = offY + x; // Pixel offset
float briNear = (imgNear.pixels[offXY] & 0xff) /
255.0;
float briFar = (imgFar.pixels[offXY] & 0xff) /
255.0;
int sel = imgSel.pixels[offXY] & 1;
// Calculate brightness - this expression can
// probably be simplified, but then it would
// become harder to follow...
float bri = (briNear * nearFade +
briFar * (1 - nearFade)) * (1 - sel) +
(briFar * farFade + briNear *
(1 - farFade)) * sel;
// Plot using a sepia hue
set(x + 1, y + 1, color(228 * bri, 212 * bri,
180 * bri));
}
}
// Vertical/near fade
fill(255 * farFade);
rect(241, 0, 9, 241);
// Horizontal/far fade
fill(255 * nearFade);
rect(0, 241, 241, 9);
// Put the text, centered
image(imgTxtNear, (241 - imgTxtNear.width) >> 1, 242);
image(imgTxtFar, 243, (241 - imgTxtFar.height) >> 1);
}