Video Effects
Because the MediaElement works like any other Silverlight element, and the VideoBrush works like any other Silverlight brush, you have the ability to manipulate video in some surprising ways. Here are some examples:
• You can use a MediaElement as the content inside a content control, such as a button.
• You can set the content for thousands of content controls at once with multiple MediaElement objects—although the client's CPU might not bear up very well under the strain.
• You can also combine video with transformations through the RenderTransform property. This allows you to move your video page, stretch it, skew it, or rotate it.
• You can set the Clipping property of the MediaElement to cut down the video page to a specific shape or path and show only a portion of the full frame.
• You can set the Opacity property to allow other content to show through behind your video. In fact, you can even stack multiple semitransparent video pages on top of each other.
• You can use an animation to change a property of the MediaElement (or one of its transforms) dynamically.
• You can copy the current content of the video page to another place in your user interface using a VideoBrush, which allows you to create specific effects like reflection.
• You can also use the same VideoBrush to paint multiple elements (or create multiple VideoBrush objects that use the same MediaElement). Both of these techniques allow you to fill multiple objects with the same video, or transformed versions of the same video.
For example, Figure 10-12 shows a video with a reflection effect underneath. It does so by creating a Grid with two rows. The top row holds a MediaElement that plays a video file. The bottom row holds a Rectangle that's painted with a VideoBrush. The video content is then flipped over by using the RelativeTransform property and then faded out gradually toward the bottom using an OpacityMask gradient.
<Grid Margin="l5" HorizontalAlignment="Center"> <Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition> </Grid.RowDefinitions>
<MediaElement x:Name="media" Source="test.wmv" Stretch="Uniform"></MediaElement>
<Rectangle Grid.Row="1" Stretch="Uniform">
<Rectangle.Fill> <VideoBrush SourceName="media"> <VideoBrush.RelativeTransform>
<ScaleTransform ScaleY="-1" CenterY="0.5"></ScaleTransform> </VideoBrush.RelativeTransform> </VideoBrush>
</Rectangle.Fill>
<Rectangle.0pacityMask> <LinearGradientBrush StartPoint="0,0" EndPoint="0,1"> <GradientStop Color="Black" 0ffset="0"></GradientStop> <GradientStop Color="Transparent" 0ffset="0.6"></GradientStop>
</LinearGradientBrush> </Rectangle.OpacityMask> </Rectangle> </Grid>
- Figure 10-12. Reflected video
This example performs fairly well. Each frame must be copied to the lower rectangle, and each frame needs to be flipped and faded to create the reflection effect. (Silverlight uses an intermediary rendering surface to perform these transformations.) But the work required to download and decode the frame of video is performed just once, and on a modern computer, the extra overhead is barely noticeable.
One of the most impressive effects in the early days of Silverlight development was a video puzzle. It took a high-resolution video file and split it into a grid of interlocking puzzle pieces, which the user could then drag apart. The effect—separate puzzle pieces, each playing a completely synchronized portion of a single video—was stunning.
With the help of the VideoBrush, creating an effect like this is almost trivial. The following example shows a slightly simplified version of the original puzzle demonstration. It starts with a single window of puzzle pieces that's divided into a configurable number of squares. When the user clicks a square in the video window, an animation moves it to a random position (as shown in Figure 10-13). Several clicks later, the video image is completely scrambled, but all the pieces are still playing the synchronized video.
- Figure 10-13. Scrambling a video while it's playing
To create this example, you first need the MediaElement that plays the video. Because all the puzzle pieces are showing portions of the same video, and you want the playback synchronized, you need just one MediaElement. It's given a Height and Width of 0 to make it invisible, so it will only appear when used through the VideoBrush.
<MediaElement x:Name="videoQip" Source="Butterfly.wmv" Height="0" Width="0" MediaEnded="videoQip_MediaEnded"></MediaElement>
When the media ends, it's started again, providing a looping playback:
private void videoClip_MediaEnded(object sender, RoutedEventArgs e) {
videoClip.Stop(); videoClip.Play();
Next, you need a layout container that will hold the puzzle pieces. In this case, a Canvas makes most sense because the animation needs to move the pieces around the page when they're clicked.
<Canvas Margin="20" x:Name="puzzleSurface" Width="300" Height="300"
Background="White" HorizontalAlignment="Center" VerticalAlignment="Center"> </Canvas>
The most interesting code happens when the Generate Puzzle button is clicked. This code calculates the size of rectangle needed to make a puzzle piece, and then dynamically creates each piece as a simple Rectangle element. Here's the code that starts it off:
private void cmdGeneratePuzzle_Click(object sender, RoutedEventArgs e) {
// Get the requested dimensions. int rows; int cols;
Int32.TryParse(txtRows.Text, out rows); Int32.TryParse(txtCols.Text, out cols);
// Clear the surface. puzzleSurface.Children.Clear();
// Determine the rectangle size.
double squareWidth = puzzleSurface.ActualWidth / cols; double squareHeight = puzzleSurface.ActualHeight / rows;
// Create the brush for the MediaElement named videoClip. VideoBrush brush = new VideoBrus (); brush.SetSource(videoClip);
// Create the rectangles. double top = 0; double left = 0;
The next step is to make sure that each Rectangle only shows the region that's assigned to it. You could accomplish this by applying a transform to the VideoBrush, but then you'd need to use a different VideoBrush object for each square. An alternate approach is to tweak the clipping region of rectangle. In this case, each rectangle gets the size of the full video window, but it's clipped to show just the appropriate region. Here's the code that creates the rectangles and sets the clipping:
// Create the rectangle. Every rectangle is sized to match the Canvas. Rectangle rect = new Rectangl (); rect.Width = puzzleSurface.ActualWidth; rect.Height = puzzleSurface.ActualHeight;
rect.Fill = brush;
SolidColorBrush rectBrush = new SolidColorBrus ( olors.Blue); rect.StrokeThickness = 3; rect.Stroke = rectBrush;
// Clip the rectangle to fit its portion of the puzzle. RectangleGeometry clip = new RectangleGeometry();
// A 1-pixel correction factor ensures there are never lines in between. clip.Rect = new Rect(left, top, squareWidth+1, squareHeight+1); rect.Clip = clip;
// Handle rectangle clicks.
rect.MouseLeftButtonDown += rect_MouseLeftButtonDown;
puzzleSurface.Children.Add(rect);
top += squareHeight;
// (If the video is not already playing, you can start it now.)
When a rectangle is clicked, the code responds by starting two animations that move it to a new, random position. Although you could create these animations manually, it's even easier to define them in the resources collection. That's because the application requires just two animations, and can reuse them for whatever square is clicked.
Here are the two animations. The animation that shifts the rectangle sideways takes 0.25 seconds, while the animation that moves it up or down takes 0.15 seconds:
<UserControl.Resources> <Storyboard x:Name="squareMoveStoryboard"> <DoubleAnimation x:Name="leftAnimation" Duration="0:0:0.25"
Storyboard.TargetProperty="(Canvas.Left)"></DoubleAnimation> <DoubleAnimation x:Name="topAnimation" Duration="0:0:0.15" Storyboard.TargetProperty="(Canvas.Top)"></DoubleAnimation> </Storyboard> </UserControl.Resources>
You'll notice that this code uses a single storyboard for all its animations. You must take extra care when reusing this storyboard. Before you can start a new animation, you must manually place the current square to its new position, and then stop the storyboard. The alternative is to dynamically create a new storyboard every time a square is clicked. (You saw this technique in action in Chapter 9, with the bomb dropping game.)
Here's the code that manages the storyboard and moves the square when it's clicked, sending it drifting to a new, random location.
private Rectangle previousRectangle;
private void rect_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) {
Rectangle rectangle = (Rectangle)sender;
// Stop the current animation.
if (previousRectangle != null) {
double left = Canvas.GetLeft(rectangle); double top = Canvas.GetTop(rectangle); squareMoveStoryboard.Stop(); Canva!.SetLeft(rectangle, left); Canvas.SetTop(rectangle, top);
// Attach the animation. squareMoveStoryboard.Stop();
// Attach the animation.
Storyboar< .SetTarget(squareMoveStoryboard, rectangle);
// Choose a random direction and movement amount. Random rand = new Randoi (); int sign = 1;
leftAnimation.To = Canvas.GetLeft(rectangle) + rand.Next(60,150) * sign; topAnimation.To = Canvas.GetTop(rectangle) + rand.Next(60, 150) * sign;
// Store a reference to the square that's being animated. previousRectangle = rectangle;
// Start the animation. squareMoveStoryboard.Begin();
This is all the code you need to complete the example, combining video, interactivity, and a rather dramatic effect that's leagues beyond other browser-based application platforms.
Average user rating: 5 stars out of 2 votes
Post a comment