Showing posts with label Reflection. Show all posts
Showing posts with label Reflection. Show all posts

Tuesday, June 29, 2010

Cloning Path Geometry in Silverlight

image

To add depth to a line rendered on a map, I experimented with the drop shadow bitmap effect. The shadow looked great but it had a heavy performance cost. As a workaround, I substituted the bitmap effect with a cloned path. Unfortunately Silverlight does not support path geometry cloning, unlike WPF, so based on a Silverlight.net forums thread I implemented a value converter that clones path geometry.

The second path below is the original street route displayed in blue above. The first path clones the blue path’s geometry and offsets it by a few pixels.

<Path Opacity="0.5"
      StrokeThickness="5"
      StrokeLineJoin="Round"
      Stroke="Gray"
      Data="{Binding ElementName=Element,
                     Path=Data,
                     Mode=OneWay,
                     Converter={StaticResource GeometryConverter}}">
    <Path.Resources>
        <local:GeometryConverter x:Key="GeometryConverter"/>
    </Path.Resources>
    <Path.RenderTransform>
        <TranslateTransform X="6" Y="6" />
    </Path.RenderTransform>
</Path>
<Path x:Name="Element"
StrokeThickness="5"
StrokeLineJoin="Round"
Stroke="Blue"/>

Below is the code for the value converter.  The code uses reflection to clone the geometry of the original path.

public class GeometryConverter : IValueConverter {
    public object Convert(object value, Type targetType,
object parameter, CultureInfo culture) { // Test for Null if (value == null) { return null; } Geometry g = value as Geometry; if (g == null) { return null; } Geometry clone = Clone(g) as Geometry; return clone; } public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture) { throw new NotImplementedException(); } private static object Clone(Object obj) { // Get all properties and clone them. PropertyInfo[] properties = obj.GetType().GetProperties(); object cloneObj = obj.GetType().GetConstructors()[0].Invoke(null); foreach (PropertyInfo property in properties) { object value = property.GetValue(obj, null); if (value != null) { if (IsPresentationFrameworkCollection(value.GetType())) { object collection = property.GetValue(obj, null); int count = (int)collection.GetType().
GetProperty("Count").GetValue(collection, null); for (int i = 0; i < count; i++) { // Get each child of the collection. object child = collection.GetType().
GetProperty("Item").
GetValue(collection, new Object[] { i }); object cloneChild = Clone(child); object cloneCollection = property.
GetValue(cloneObj, null); collection.GetType().
InvokeMember("Add",
BindingFlags.InvokeMethod,
null,
cloneCollection,
new object[] { cloneChild }); } } // If the property is a UIElement, we also need to clone it. else if (value is UIElement) { object obj2 = property.PropertyType.
GetConstructors()[0].Invoke(null); Clone(obj2); property.SetValue(cloneObj, obj2, null); } // For a normal property, its value doesn't need to be
// cloned.
So just copy its value to the new object. else if (property.CanWrite) { property.SetValue(cloneObj, value, null); } } } return cloneObj; } private static bool IsPresentationFrameworkCollection(Type type) { if (type == typeof(object)) { return false; } if (type.Name.StartsWith("PresentationFrameworkCollection")) { return true; } return IsPresentationFrameworkCollection(type.BaseType); } }

Monday, November 23, 2009

Silverlight Coverflow with Reflection

This post describes how to create an attractive and compelling cover flow control using Silverlight as illustrated below.

Silverlight Coverflow with Reflection

The basis of this sample is the excellent work done by Darick C with his Silverlight Cover Flow project published on CodePlex.  To begin, download Darick’s Cover Flow contribution.  Create a new Silverlight application called “CoverFlowWithReflection” in Microsoft Visual Studio 2008, and add reference to either the Coverflow project or binary.

Below is the XAML for MainPage.xaml.  Each cover flow item is defined with an image and a textbox.

<UserControl
    x:Class="CoverFlowWithReflection.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    mc:Ignorable="d"
    d:DesignWidth="640"
    d:DesignHeight="480"
    xmlns:c="clr-namespace:ControlLibrary;assembly=ControlLibrary"
    >
    <Grid>
        <c:CoverFlowControl x:Name="CoverFlow" Margin="0,10,0,0">
            <c:CoverFlowControl.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <Image Source="{Binding Path=Picture, Mode=OneWay}"
                               Height="300"
                               Stretch="UniformToFill"
                               />
                        <TextBlock Text="{Binding Path=Name, Mode=OneWay}"
                                   Foreground="White"
                                   HorizontalAlignment="Center"
                                   FontSize="12"
                                   />
                    </Grid>
                </DataTemplate>
            </c:CoverFlowControl.ItemTemplate>
        </c:CoverFlowControl>
    </Grid>
</UserControl>

The following snippet is for the MainPage.xaml code-behind (or MainPage.xaml.cs).  The code defines a new generic collection and assigns it to the coverflow control.  Please excuse the crazy formatting, this is required to squeeze the text into the blog.

using System.Collections.ObjectModel;
using System.Windows.Controls;

namespace CoverFlowWithReflection {
    public partial class MainPage : UserControl {
        private const string PREFIX =
             "http://upload.wikimedia.org/wikipedia/commons/thumb/";
        private ObservableCollection<Thumbnail> _thumbnails =
            new ObservableCollection<Thumbnail>();
        //
        // CONSTRUCTOR
        //
        public MainPage() {
            InitializeComponent();

            // Populate thumbnail collection
            this._thumbnails.Add(
                new Thumbnail {
                    Name = "Albert Einstein",
                    Picture = PREFIX +
                        "7/78/Einstein1921_by_F_Schmutzer_4.jpg" +
                        "/225px-Einstein1921_by_F_Schmutzer_4.jpg"
                }
            );
            this._thumbnails.Add(
                new Thumbnail {
                    Name = "Isaac Newton",
                    Picture = PREFIX +
                        "3/39/GodfreyKneller-IsaacNewton-1689.jpg" +
                        "/225px-GodfreyKneller-IsaacNewton-1689.jpg"
                }
            );
            this._thumbnails.Add(
                new Thumbnail {
                    Name = "Gottfried Leibniz",
                    Picture = PREFIX +
                        "6/6a/Gottfried_Wilhelm_von_Leibniz.jpg" +
                        "/200px-Gottfried_Wilhelm_von_Leibniz.jpg"
                }
            );
            this._thumbnails.Add(
                new Thumbnail {
                    Name = "Niels Bohr",
                    Picture = PREFIX + 
                        "6/6d/Niels_Bohr.jpg" +
                        "/180px-Niels_Bohr.jpg"
                }
            );
            this._thumbnails.Add(
                new Thumbnail {
                    Name = "Ernest Rutherford",
                    Picture = PREFIX +
                        "5/57/Ernest_Rutherford2.jpg" +
                        "/200px-Ernest_Rutherford2.jpg"
                }
            );
            this._thumbnails.Add(
                new Thumbnail {
                    Name = "Stephen Hawking",
                    Picture = PREFIX +
                        "e/eb/Stephen_Hawking.StarChild.jpg" +
                        "/200px-Stephen_Hawking.StarChild.jpg"
                }
            );
            this._thumbnails.Add(
                new Thumbnail {
                    Name = "Galileo Galilei",
                    Picture = PREFIX +
                        "c/cc/Galileo.arp.300pix.jpg" +
                        "/225px-Galileo.arp.300pix.jpg"
                }
            );
            this._thumbnails.Add(
                new Thumbnail {
                    Name = "Carl Friedrich Gauss",
                    Picture = PREFIX +
                        "9/9b/Carl_Friedrich_Gauss.jpg" +
                        "/225px-Carl_Friedrich_Gauss.jpg"
                }
            );
            this._thumbnails.Add(
                new Thumbnail {
                    Name = "J. Robert Oppenheimer",
                    Picture = PREFIX +
                        "0/03/JROppenheimer-LosAlamos.jpg" +
                        "/200px-JROppenheimer-LosAlamos.jpg"
                }
            );

            // Assign collection to CoverFlow control
            this.CoverFlow.ItemsSource = _thumbnails;
        }
    }
    public class Thumbnail {
        public string Name { get; set; }
        public string Picture { get; set; }
    }
}

The result of this code is a functional coverflow control as shown below.

Plain cover flow

The control is excellent for presenting a catalog of information and also providing an organic look and feel.  The following two snippet will demonstrate how to add depth to this control.  First, define a transitional background.

<Grid>
    <Grid.Background>
        <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
            <GradientStop Color="#000000" Offset="0.0"/>
            <GradientStop Color="#000000" Offset="0.6"/>
            <GradientStop Color="#131212" Offset="0.7"/>
            <GradientStop Color="#3A3535" Offset="0.8"/>
            <GradientStop Color="#625A5A" Offset="0.9"/>
            <GradientStop Color="#808080" Offset="1.0"/>
        </LinearGradientBrush>
    </Grid.Background>
    <c:CoverFlowControl x:Name="CoverFlow" Margin="0,10,0,0">
        <!-- omitted for clarity  -->
    </c:CoverFlowControl>
</Grid>

This gives the illusion that the coverflow items (images in this case) are residing on a surface with a little perspective.

Cover flow with background

And lastly, to the complete the visual effect, lets add a reflection.  This is achieved by adding a second image to the coverflow item data template.  A RenderTransfrom and OpacityMask are used to flip and fade the image respectively.

<c:CoverFlowControl x:Name="CoverFlow" Margin="0,10,0,0">
    <c:CoverFlowControl.ItemTemplate>
        <DataTemplate>
            <Grid>
                <Image Source="{Binding Path=Picture, Mode=OneWay}"
                       Height="300"
                       Stretch="UniformToFill"
                       />
                <TextBlock Text="{Binding Path=Name, Mode=OneWay}"
                           Foreground="White"
                           HorizontalAlignment="Center"
                           FontSize="12"
                           />
                <Image Source="{Binding Path=Picture, Mode=OneWay}"
                       Height="300"
                       Stretch="UniformToFill"
                       RenderTransformOrigin="0.5,1.0">
                    <Image.RenderTransform>
                        <TransformGroup>
                            <ScaleTransform ScaleY="-1"/>
                            <TranslateTransform Y="3"/>
                        </TransformGroup>
                    </Image.RenderTransform>
                    <Image.OpacityMask>
                        <LinearGradientBrush StartPoint="0.5,0"
                                             EndPoint="0.5,1">
                            <GradientStop Color="#00000000" Offset="0.0"/>
                            <GradientStop Color="#00000000" Offset="0.7"/>
                            <GradientStop Color="#FFFFFFFF" Offset="1.0"/>
                        </LinearGradientBrush>
                    </Image.OpacityMask>
                </Image>
            </Grid>
        </DataTemplate>
    </c:CoverFlowControl.ItemTemplate>
</c:CoverFlowControl>

The result is an attractive clowflow with reflection.

Cover flow with background and reflection

There are two alternatives to this technique that you may want to consider.  The first is to use a pixel shader in Silverlight 3, Steve from Cellbi describes how to create a reflection pixel shader in this post.  The second is to make use of Silverlight 3’s WriteableBitmap class to generate a reflection.  Jeff Prosise from Wintellect describes the technique in this post.

The source code for this sample application is here:
http://cid-dd16c3f34f4d913e.skydrive.live.com/self.aspx/Kiwigis/CoverFlowWithReflection.zip