今年早些时候,雅虎关闭了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与要渲染的元素。
这个控件可以用来渲染任何东西的反射(甚至是一个反射的反射,如果愿意的话)。这里有一个更复杂的例子: