As I demonstrated in a previous blog entry, the new Matrix3DProjection class in Silverlight 3 lets you define 2D non-affine transforms to shift the sides of any Silverlight element into an arbitrary convex quadrilateral. But what if you want to get more extreme? What if you want to make a bitmap look like a fluttering flag, for example?
That's possible as well, as the following demo program shows:
You might want to upgrade your machine to run this one!
Here's the source code. I created the project under Visual Studio 2010, but it would be easy enough to copy the Page.xaml and Page.xaml.cs files into a new Visual Studio 2008 project.
The first step to manipulating an image (or some other element) in this way is to cut it up into pieces — a bunch of tiny square bitmaps all the same size — and assemble them in a grid. The WriteableBitmap class (also new in Silverlight 3) is ideal for this purpose. (Actually, I think it's pretty much essential because I don't see another way to do it in Silverlight code.) The code is in the PrepareImage method in Page.xaml.cs. From what I can gather, the FrameworkElement derivative you pass to the Render method of WriteableBitmap must be a visible element in layout, but that element — or a part of it with an optional transform — is plastered onto the surface of the bitmap. Notice that each of these tiny bitmaps encompasses 20 square pixels of the original image, but I made the bitmaps 21 pixels square so they slightly overlap and avoid thin white lines between them that appeared otherwise.
Now that the image has been reproduced with a grid of tiny bitmaps, each of these bitmaps can be subjected to a different 2D non-affine transform. If you make sure the corners always meet and that the transforms don't result in non-convex quadrilaterals, if will seem as if the whole image is being warped in some way.
That's the purpose of the TransformImage method in Page.xaml.cs. For this particular effect I used two animated sine curves, one for the top and one for the bottom, and just interpolated between the two to move the coordinates of the grid around. From these shifted coordinates, the CalculateNewTransform method (very similar to the one in that earlier blog entry) calculates a Matrix3D for each little bitmap in the grid.
I'll post another example of this technique in a day or two.