« Up close and personal ... | Main | Why you should avoid a Windows Home Server »

Flipping images in 3D!

Ever wanted to flip some photos in 3D in WPF?

Well, I did. (honestly, I wanted to do with cover art and ITunes, but ITunes was throwing far too many "CATASTROPHIC" errors on it's COM interface that I gave up!)

Here's my sample project demonstrating flipping images in 3D using WPF. 12 x 9 photo thumbnails are loaded, and then semi-randomly, each image is flipped and replaced with a new thumbnail -- over and over.

image

The source code to this WPF 3.0 application is available for download here.

As coded, it scans your "Photos" directory in XP/Vista  and then slowly starts flipping each of the small thumbnails through your images. You can move around the viewport using "S" for Forward and "X" for backward and the cursor keys adjust the direction you're looking (up, down, left, or right).

image

 

image

The main XAML for the Window is simple:

<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="ImageFlip.Window1"
    x:Name="Window"
    Title="ImageFlip in 3D!"
    Width="640" Height="480" Foreground="#FFF4F4F4">

<Window.Background>
<
LinearGradientBrush EndPoint="0.001,0.044" StartPoint="0.001,0.5">
<
GradientStop Color="#FF000000" Offset="0"/>
<
GradientStop Color="#FF1C1C1C" Offset="1"/>
</
LinearGradientBrush>
</
Window.Background>

<Grid x:Name="LayoutRoot">
<
Viewport3D x:Name="vp3D">
<
Viewport3D.Resources>
</
Viewport3D.Resources>
<
Viewport3D.Camera>
<
PerspectiveCamera x:Name="cam"
FarPlaneDistance="45"
LookDirection="0,0,-1"
UpDirection="0,1,0"
NearPlaneDistance="1"
Position="0,0,10"
FieldOfView="45" />
</
Viewport3D.Camera>
</
Viewport3D>
</
Grid>
</
Window>


Nothing more really than a place to hold the 3D viewport and the camera.


I create each image as a pure rectangle (rather than trying to make images that fit, they're stretched a bit for the purposes of this code):

MeshGeometry3D mesh3d = new MeshGeometry3D();
mesh3d.Positions.Add(new Point3D(-0.5, 0.5, 0));
mesh3d.Positions.Add(new Point3D(0.5, 0.5, 0));
mesh3d.Positions.Add(new Point3D(-0.5, -0.5, 0));
mesh3d.Positions.Add(new Point3D(0.5, -0.5, 0));

mesh3d.TriangleIndices.Add(0);
mesh3d.TriangleIndices.Add(2);
mesh3d.TriangleIndices.Add(1);
mesh3d.TriangleIndices.Add(1);
mesh3d.TriangleIndices.Add(2);
mesh3d.TriangleIndices.Add(3);

mesh3d.TextureCoordinates.Add(new Point(0, 0));
mesh3d.TextureCoordinates.Add(new Point(1, 0));
mesh3d.TextureCoordinates.Add(new Point(0, 1));
mesh3d.TextureCoordinates.Add(new Point(1, 1));

image 

I set up some Rotation:

Transform3DGroup transformGroup = new Transform3DGroup();

// gotta set where we want to rotate around. We don't want to rotate around the world axis, which would be the default ...
RotateTransform3D rotateTransform = new RotateTransform3D();
rotateTransform.CenterX = xOffset;
rotateTransform.CenterZ = zOffset;
rotateTransform.CenterY = yOffset;
// we'll need a default axis angle so that we can animate it later ...
rotateTransform.Rotation = new AxisAngleRotation3D(new Vector3D(0, 0, 0), 0);
// move the object into the proper location in 3d space ...
transformGroup.Children.Add(new TranslateTransform3D(xOffset, yOffset, zOffset));
// add the rotation
transformGroup.Children.Add(rotateTransform);
// throw them together and put them into the group
gm3d.Transform = transformGroup;

_model3dGroup.Children.Add(gm3d);

AxisAngleRotation3D axis = new AxisAngleRotation3D(new Vector3D(0, 1, 0), 180);
AxisAngleRotation3D axisFrom = new AxisAngleRotation3D(new Vector3D(0, 1, 0), 0);
Random rnd = new Random();
double rotateTime = rnd.NextDouble() * 5 + 2;

Rotation3DAnimationPlus rotate3d = new
Rotation3DAnimationPlus(gm3d,
rotateTransform,
axis, axisFrom,
new Duration(TimeSpan.FromSeconds(rotateTime)),
FillBehavior.HoldEnd, false);
rotateTransform.BeginAnimation(RotateTransform3D.RotationProperty, rotate3d);


The key about this is that the RotateTransform3D Center points (CenterX, etc.) are all based on where I've actually placed the image. By default, the images are all created on the origin of the Viewport (0, 0, 0). Because of that, I move them to a new location (the TranslateTransform3D object), then set their rotate based on that point. If the code didn't set the rotate transform based on their actual locations, they'd rotate around the origin, which although neat, wasn't the look I wanted.


I've created a new Rotation class (Rotation3DAnimationPlus), to handle swapping in new images automatically when the animation completes.


I've tried to do as much of the work in background threads, but WPF requires that some image work be done in the main UI thread unfortunately, so the animation isn't as smooth as I'd like (at least in my experience when loading images over the network). The code tries to grab thumbnails or create thumbnails rather than using the full size images. Thus, if you zoom, you'll see that the images are pixelated. You can disable that easily enough in code, but the memory requirements for the code shoot skyward!


I haven't tested the code thoroughly by any means, so it may not be suitable for anything other than a demo like this.


image

Help support my web site by searching and buying through Amazon.com (in assocation with Amazon.com).