动态反射效果的复兴

今年早些时候,雅虎关闭了GeoCities的消息让感到遗憾。在GeoCities的深处,埋藏着创建的第一个网页,它有“建设中”横幅、动画GIF、访客簿和刺耳的背景音乐。不幸的是,不知道那个页面的URL是什么,而且那还是谷歌主宰生活之前很久的事情!

这篇博客文章是对90年代流行的动态效果之一——动态反射的致敬。Java小程序曾经非常流行,但它们和GeoCities一样,已经消失了。也许是时候复兴了?

上面的Silverlight应用程序包含了一个用户控件,它渲染了一个引用的FrameworkElement的动画反射。

产生涟漪效果的代码非常简单,DispatcherTimer递增_time并重新绘制反射。反射本身是通过从引用元素构建WriteableBitmap来实现的,这使能够获取其像素值。另一个WriteableBitmap用于反射图像,通过适当Y偏移复制像素行来产生涟漪效果:

private double _time = 0.0; private void Timer_Tick(object sender, EventArgs e) { // increment phi and update the reflection _time += 0.4; UpdateReflection(); } /// /// Copies an inverted image of the referenced FrameworkElement /// with a 'ripple' effect /// /// private void UpdateReflection() { FrameworkElement reflectedFE = ReflectedElement as FrameworkElement; if (reflectedFE == null) return; // synchronize the element width Width = reflectedFE.ActualWidth; // copy the source into a writeable bitmap WriteableBitmap sourceBitmap = new WriteableBitmap(reflectedFE, null); // create a target which is the same width / height as the reflection element WriteableBitmap targetBitmap = new WriteableBitmap((int)ActualWidth, (int)ActualHeight); // copy the reflection for (int y = 0; y < targetBitmap.PixelHeight; y++) { double amplitude = ComputeAmplitude(y, targetBitmap.PixelHeight); double sinusoid = ComputeRipple(y, targetBitmap.PixelHeight, _time); // the offset to the y value index caused by the ripple int yOffset = (int)(sinusoid * amplitude); // compute the Y position of the line to copy from the source image int sourceYLocation = sourceBitmap.PixelHeight - 1 - ((y + yOffset) * sourceBitmap.PixelHeight) / targetBitmap.PixelHeight; // check that this value is in range sourceYLocation = Math.Min(sourceBitmap.PixelHeight - 1, Math.Max(0, sourceYLocation)); // copy the required row int sourceIndex = sourceYLocation * sourceBitmap.PixelWidth; int targetIndex = y * targetBitmap.PixelWidth; for (int i = 0; i < targetBitmap.PixelWidth; i++) { targetBitmap.Pixels[targetIndex++] = sourceBitmap.Pixels[sourceIndex++]; } } targetBitmap.Invalidate(); LayoutRoot.Source = targetBitmap; } /// /// Compute the amplitude of the oscillations at a given Y position /// /// private double ComputeAmplitude(int y, int height) { // our amplitude range is 1 to 3 return ((double)y * 2) / (double)height + 1.0; } /// /// Compute the sinusoid applied to the image at the given location /// /// private double ComputeRipple(int y, int height, double time) { // provide a ripple that is the combination of two out of phase sine waves double phaseFactor = (double)y / (double)height; return Math.Sin(time + phaseFactor * 16) + Math.Sin(time + phaseFactor * 30) + Math.Sin(time + phaseFactor * 62); }

这个用户控件的XAML非常简单,它是一个带有透明度渐变的图像:

<UserControl x:Class="SilverlightShimmer.ReflectionControl" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="300" Height="300"> <Image x:Name="LayoutRoot"> <Image.OpacityMask> <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0"> <GradientStop Color="#FF000000" Offset="0"/> <GradientStop Color="#00000000" Offset="1"/> </LinearGradientBrush> </Image.OpacityMask> </Image> </UserControl>

这个控件与圣诞节图像关联如下:

<UserControl x:Class="SilverlightShimmer.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:SilverlightShimmer" Width="320" Height="260"> <Grid Background="Black"> <StackPanel Orientation="Vertical" Margin="10"> <Border x:Name="controlToReflect" BorderThickness="5" BorderBrush="LightGray" CornerRadius="3" HorizontalAlignment="Center"> <Image Source="christmas.jpg" Margin="3" Stretch="None"/> </Border> <local:ReflectionControl x:Name="shimmer" Height="80" Margin="3" ReflectedElement="{Binding ElementName=controlToReflect}"/> </StackPanel> </Grid> </UserControl>

这里有一个有趣的点,即Border和Image与ReflectionControl的关联方式。ReflectedElement属性通过ElementName绑定到Border,但这个绑定没有Path。因此,ReflectedElement绑定到元素本身,而不是绑定到引用元素的属性,因此不需要任何代码后台来关联ReflectionControl与要渲染的元素。

这个控件可以用来渲染任何东西的反射(甚至是一个反射的反射,如果愿意的话)。这里有一个更复杂的例子:

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485