Professional, Software

Stack3D Image Viewer

For my Search application, I wanted to create something little bit nicer than typical HTML UI. So I created Stack3D Image Viewer, that looks like this.  

The way I wanted to build this was to separate actual fact that it shows image from the visualization itself. so It is pretty easy to change this control to show the list of Videos in 3D stack instead of images. To have it this way, there are two controls in this sample…

  1. PictureFrame : This control knows how to show an image and that is it. Somebody could change this to VideoFrame or even nicer looking picture frame
  2. Stack3D: This control knows how to show Frameworkelement in 3D stack. I could not get this abstraction fully working because in the UpdateLayout method, I need to set the actual height and width of picture frame. This is because I override Height/Width property of Control class and when I set height and width of FrameworkElement, my ImageFrame control does not get those values.

Stack3D control has two basic interaction APIs. First is Frames, this is basically frames that need to be shown in 3D space and Second is NumberOfFrames which decided how many frames are visible on the screen at any given time.

Stack 3D control, give these two set of information, calculate where to draw each of frame. It also defines movement of those frames in the space by calculating Top/Left and ScaleTransformation. I could not animate Height and Width for the same reason (overridden in PictureFrame control and Animation engine yet does not know how to animate custom properties) but I think better way to do this is through scaletransformation instead of actually animating height and width.

This is neither fully functional control nor the best way to build it. E.g. I think the better way to do this is to create number of picture frames = (2 + Number of visible frames set by NumberOfFrames property) and rotate them. But this was easy for me to build 🙂 and i was in hurry to see my picture rotating in virtual 3D space :).

Since Stack3D defines movement and behavior in virtual 3D space, I attach animation to individual PictureFrame controls on the fly.

Source for PictureFrame.xaml

<Canvas
    xmlns="http://schemas.microsoft.com/client/2007"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Width="320" Height="240" x:Name="_rootCanvas">
    <Rectangle Fill="#FFFFFFFF" Stroke="#FF000000" StrokeThickness="5" RadiusX="5" RadiusY="5" x:Name="rectborder" Width="320" Height="240"/>
  <Image x:Name="image" Width="300" Height="220" Canvas.Left="10" Canvas.Top="10" />
    
</Canvas>

Source for PictureFrame.xaml.cs

using System;
using System.IO;
using System.Linq;
using System.Xml;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace Visualization
{
    public class PictureFrame : Control
    {
        private Canvas _rootCanvas;
        private Image _image;
        private Rectangle _rectborder;

        public PictureFrame()
        {
            Stream s = this.GetType().Assembly.GetManifestResourceStream("Visualization.PictureFrame.xaml");
            _rootCanvas = (Canvas)this.InitializeFromXaml(new StreamReader(s).ReadToEnd());
            _image = (Image)_rootCanvas.FindName("image");
            _rectborder = (Rectangle)_rootCanvas.FindName("rectborder");
            _rootCanvas.Loaded += new EventHandler(_rootCanvas_Loaded);
        }

        void _rootCanvas_Loaded(object sender, EventArgs e)
        {
            UpdateLayout();
        }

        public Uri Source
        {
            get { return _image.Source; }
            set { _image.Source = value; }
        }

        private void UpdateLayout()
        {
            _rootCanvas.Height = Height;
            _rootCanvas.Width = Width;
            _image.Height = Height - 20;
            _image.Width = Width - 20;
            _rectborder.Height = Height;
            _rectborder.Width = Width;
        }


        public new double Height
        {
            get { return ((FrameworkElement)this).Height; }
            set
            {
                ((FrameworkElement)this).Height = value;
                UpdateLayout();
            }
        }

        public new double Width
        {
            get { return ((FrameworkElement)this).Width; }
            set
            {
                ((FrameworkElement)this).Width = value;
                UpdateLayout();
            }
        }

        public double FrameHeight
        {
            get { return Height; }
            set
            {
                Height = value;
            }
        }

        public double FrameWidth
        {
            get { return Width; }
            set
            {
                Width = value;
            }
        }

    }
}

Source for StackThreeD.xaml

<Canvas xmlns="http://schemas.microsoft.com/client/2007"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Width="640"
        Height="480">

</Canvas>

Source for StackThreeD.xaml.cs

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
using System.IO;

namespace Visualization
{
    public class StackThreeD : Control
    {
        private Canvas _rootCanvas;
        private Collection<Point> _points;
        private Collection<Size> _sizes;
        private int _numberOfFrames = 2;
        private Collection<FrameworkElement> _frames;
        private Collection<Storyboard> _movestoryboards;
        private Collection<FrameworkElement> _visibleframes;

        private int start = 0;

        public StackThreeD()
        {
            Stream s = this.GetType().Assembly.GetManifestResourceStream("Visualization.StackThreeD.xaml");
            _rootCanvas = (Canvas)this.InitializeFromXaml(new System.IO.StreamReader(s).ReadToEnd());
            _rootCanvas.Loaded += new EventHandler(_rootCanvas_Loaded);
        }

        public int NumberOfFrames
        {
            get { return _numberOfFrames; }
            set
            {
                _numberOfFrames = value;
                UpdateLayout();
            }
        }

        public void MoveNext()
        {
            if (_frames != null)
            {
                start = start - 1;
                _visibleframes = new Collection<FrameworkElement>();
                if (start < 0)
                    start = start + _frames.Count;

                int counter = start;

                for (int i = 0; i < _numberOfFrames; i++)
                {
                    if (counter >= _frames.Count)
                        counter = _frames.Count - counter;
                    if (i == 0)
                        MoveFrame(counter, _points[i], _sizes[i], true);
                    else
                        MoveFrame(counter, _points[i], _sizes[i], false);
                    counter++;
                }

                //hide last frame
                if (counter >= _frames.Count)
                    counter = _frames.Count - counter;
                HideFrame(counter);

                //set zindex properly
                int j = 0;
                foreach (FrameworkElement visibleframe in _visibleframes)
                    visibleframe.SetValue(UIElement.ZIndexProperty, j++);
            }
        }

        private void HideFrame(int index)
        {
            Point startpoint = new Point((double)_frames[index].GetValue(Canvas.LeftProperty), (double)_frames[index].GetValue(Canvas.TopProperty));
            Point endpoint = new Point(Width, Height);
            AnimateFrame(index, startpoint, endpoint, 1, 2, _numberOfFrames);
        }

        private void MoveFrame(int index, Point p, Size s, bool isfirst)
        {
            double startscale;
            _visibleframes.Add(_frames[index]);
            _frames[index].Visibility = Visibility.Visible;

            if (isfirst == true)
            {
                _frames[index].SetValue(Canvas.TopProperty, 0);
                _frames[index].SetValue(Canvas.LeftProperty, 0);
                startscale = 0.0001;

            }
            else
                startscale = ((ScaleTransform)((TransformGroup)_frames[index].RenderTransform).Children[0]).ScaleY;

            Point startpoint = new Point((double)_frames[index].GetValue(Canvas.LeftProperty), (double)_frames[index].GetValue(Canvas.TopProperty));
            double endscale = (s.Width / (Width / 2));
            AnimateFrame(index, startpoint, p, startscale, endscale, 0);

        }

        private void AnimateFrame(int index, Point startpoint, Point endpoint, double startscale, double endscale, int zindex)
        {
            if (index != 0)
                _frames[index].SetValue(UIElement.ZIndexProperty, zindex);
            DoubleAnimation topanimation = (DoubleAnimation)_movestoryboards[index].FindName("topanimation" + index.ToString());
            DoubleAnimation leftanimation = (DoubleAnimation)_movestoryboards[index].FindName("leftanimation" + index.ToString());
            DoubleAnimation scalexanimation = (DoubleAnimation)_movestoryboards[index].FindName("scalexanimation" + index.ToString());
            DoubleAnimation scaleyanimation = (DoubleAnimation)_movestoryboards[index].FindName("scaleyanimation" + index.ToString());

            scaleyanimation.From = startscale;
            scalexanimation.From = startscale;
            scalexanimation.To = endscale;
            scaleyanimation.To = endscale;

            topanimation.From = startpoint.Y;
            leftanimation.From = startpoint.X;
            topanimation.To = endpoint.Y;
            leftanimation.To = endpoint.X;

            _movestoryboards[index].Begin();

        }


        public Collection<FrameworkElement> Frames
        {
            get { return _frames; }
            set
            {
                _frames = value;
                _rootCanvas.Children.Clear();
                PopulateTree();
                UpdateLayout();
            }
        }

        private void _rootCanvas_Loaded(object sender, EventArgs e)
        {
            UpdateLayout();
        }



        private void PopulateTree()
        {
            int counter = 0;
            start = 0;
            _movestoryboards = new Collection<Storyboard>();
            foreach (FrameworkElement pf in _frames)
            {
                _rootCanvas.Children.Add(pf);
                pf.SetValue(DependencyObject.NameProperty, counter.ToString());
                string _transformstring;
                Stream s = this.GetType().Assembly.GetManifestResourceStream("Visualization.FrameTransform.xaml");
                _transformstring = new StreamReader(s).ReadToEnd();
                TransformGroup tg = (TransformGroup)XamlReader.Load(_transformstring);
                pf.RenderTransform = tg;
                AttachAnimation(pf);
                counter++;
            }
        }

        private void AttachAnimation(FrameworkElement pf)
        {
            string _frameanimation;
            Stream s = this.GetType().Assembly.GetManifestResourceStream("Visualization.FrameAnimation.xaml");
            _frameanimation = new StreamReader(s).ReadToEnd();
            _frameanimation = _frameanimation.Replace("frame", pf.Name);
            _frameanimation = _frameanimation.Replace("scalexanimation", "scalexanimation" + pf.Name);
            _frameanimation = _frameanimation.Replace("scaleyanimation", "scaleyanimation" + pf.Name);
            _frameanimation = _frameanimation.Replace("topanimation", "topanimation" + pf.Name);
            _frameanimation = _frameanimation.Replace("leftanimation", "leftanimation" + pf.Name);
            Storyboard sb = (Storyboard)XamlReader.Load(_frameanimation);
            _rootCanvas.Resources.Add(sb);
            _movestoryboards.Add(sb);
        }

        private void UpdateLayout()
        {

            _rootCanvas.Height = Height;
            _rootCanvas.Width = Width;
            //update clipping area
            RectangleGeometry clip = new RectangleGeometry();
            clip.Rect = new Rect(0, 0, Width, Height);
            _rootCanvas.Clip = clip;
            if (_frames != null)
            {
                CalculateSteps();
                foreach (FrameworkElement pf in _frames)
                {
                    pf.Visibility = Visibility.Hidden;
                    //layout
                    ((PictureFrame)pf).Height = Height / 2;
                    ((PictureFrame)pf).Width = Width / 2;
                }
                for (int i = 0; i < _numberOfFrames; i++)
                {
                    FrameworkElement pf = _frames[i];
                    ((ScaleTransform)((TransformGroup)pf.RenderTransform).Children[0]).ScaleX = (_sizes[i].Width / (Width / 2));
                    ((ScaleTransform)((TransformGroup)pf.RenderTransform).Children[0]).ScaleY = (_sizes[i].Height / (Height / 2)); ;
                    pf.SetValue(Canvas.TopProperty, _points[i].Y);
                    pf.SetValue(Canvas.LeftProperty, _points[i].X);
                    pf.Visibility = Visibility.Visible;
                    pf.SetValue(UIElement.ZIndexProperty, i);
                }
            }
        }

        private void CalculateSteps()
        {
            _points = new Collection<Point>();
            _sizes = new Collection<Size>();

            double xstep = Width / (2 * _numberOfFrames);
            double ystep = Height / (2 * _numberOfFrames);

            Point startpoint = new Point(xstep, ystep);
            Size startsize = new Size(2 * xstep, 2 * ystep);

            Point endpoint = new Point(Width / 2, Height / 2);
            Size endsize = new Size(Width / 2, Height / 2);

            _points.Add(startpoint);
            _sizes.Add(startsize);

            double widthstep = (Width / 2) / (_numberOfFrames + 1);
            double heightstep = (Height / 2) / (_numberOfFrames + 1);

            for (int i = 1; i < (_numberOfFrames - 1); i++)
            {
                Point p = new Point(startpoint.X + (i * xstep), startpoint.Y + (i * ystep));
                Size s = new Size(startsize.Width + (i * widthstep), startsize.Height + (i * heightstep));
                _points.Add(p);
                _sizes.Add(s);
            }

            _points.Add(endpoint);
            _sizes.Add(endsize);
        }

        public new double Height
        {
            get { return ((FrameworkElement)this).Height; }
            set
            {
                ((FrameworkElement)this).Height = value;
                UpdateLayout();
            }
        }

        public new double Width
        {
            get { return ((FrameworkElement)this).Width; }
            set
            {
                ((FrameworkElement)this).Width = value;
                UpdateLayout();
            }
        }
    }
}

Source for FrameAnimation.xaml

<Storyboard >
  <DoubleAnimation Name="leftanimation" From="0" To="10" Duration="0:0:0.5" Storyboard.TargetName="frame" Storyboard.TargetProperty="(Canvas.Left)"/>
  <DoubleAnimation Name="topanimation" From="0" To="10" Duration="0:0:0.5" Storyboard.TargetName="frame" Storyboard.TargetProperty="(Canvas.Top)"/>
  <DoubleAnimation Name="scalexanimation" From="1" To="2" Duration="0:0:0.5" Storyboard.TargetName="frame" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)"/>
  <DoubleAnimation Name="scaleyanimation" From="1" To="2" Duration="0:0:0.5" Storyboard.TargetName="frame" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)"/>
</Storyboard>

Source for FrameTransform.xaml

<TransformGroup>
  <ScaleTransform ScaleX="1" ScaleY="1"/>
  <SkewTransform AngleX="0" AngleY="0"/>
  <RotateTransform Angle="0"/>
  <TranslateTransform X="0" Y="0"/>
</TransformGroup>

I will try to post the whole zip as soon as possible since MSDN blogs seems to be not working at the moment. But here is how to use this control…

Page.xaml

<Canvas
        xmlns="http://schemas.microsoft.com/client/2007"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:Custom="clr-namespace:Visualization;assembly=ClientBin/Visualization.dll"
        x:Name="parentCanvas"
        Loaded="Page_Loaded"
        x:Class="PictureStackApplication.Page;assembly=ClientBin/PictureStackApplication.dll"
        Width="1000"
        Height="1000"
        >
  <Custom:StackThreeD Name="picturestack" Height="600" Width="800" Canvas.Top="0" Canvas.Left="0" MouseLeftButtonDown="OnMouseLeftButtonDown"/>
</Canvas>

Page.xaml.cs

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Interop;
using System.Collections.ObjectModel;
using Visualization;

namespace PictureStackApplication
{
    public partial class Page : Canvas
    {
        Collection<FrameworkElement> _pictures;
        StackThreeD ps;

        public void Page_Loaded(object o, EventArgs e)
        {
            // Required to initialize variables
            InitializeComponent();
            BrowserHost.Resize += new EventHandler(BrowserHost_Resize);
            AddFakePictures();
            ps = (StackThreeD)FindName("picturestack");
            ps.NumberOfFrames = 10;
            ps.Frames = _pictures;
        }

        void BrowserHost_Resize(object sender, EventArgs e)
        {
            Width = BrowserHost.ActualWidth;
            Height = BrowserHost.ActualHeight;
            ps.Height = Height;
            ps.Width = Width;
        }

        void OnMouseLeftButtonDown(object sender, MouseEventArgs e)
        {
            ps.MoveNext();
        }

        private void AddFakePictures()
        {
            _pictures = new Collection<FrameworkElement>();

            PictureFrame pf = new PictureFrame();
            _pictures.Add(pf);
            pf.Source = new Uri("Images/Autumn Leaves.jpg", UriKind.Relative);

            pf = new PictureFrame();
            _pictures.Add(pf);
            pf.Source = new Uri("Images/Creek.jpg", UriKind.Relative);

            pf = new PictureFrame();
            _pictures.Add(pf);
            pf.Source = new Uri("Images/Desert Landscape.jpg", UriKind.Relative);

            pf = new PictureFrame();
            _pictures.Add(pf);
            pf.Source = new Uri("Images/Dock.jpg", UriKind.Relative);

            pf = new PictureFrame();
            _pictures.Add(pf);
            pf.Source = new Uri("Images/Forest.jpg", UriKind.Relative);

            pf = new PictureFrame();
            _pictures.Add(pf);
            pf.Source = new Uri("Images/Forest Flowers.jpg", UriKind.Relative);

            pf = new PictureFrame();
            _pictures.Add(pf);
            pf.Source = new Uri("Images/Frangipani Flowers.jpg", UriKind.Relative);

            pf = new PictureFrame();
            _pictures.Add(pf);
            pf.Source = new Uri("Images/Garden.jpg", UriKind.Relative);

            pf = new PictureFrame();
            _pictures.Add(pf);
            pf.Source = new Uri("Images/Green Sea Turtle.jpg", UriKind.Relative);

            pf = new PictureFrame();
            _pictures.Add(pf);
            pf.Source = new Uri("Images/Humpback Whale.jpg", UriKind.Relative);

            pf = new PictureFrame();
            _pictures.Add(pf);
            pf.Source = new Uri("Images/Oryx Antelope.jpg", UriKind.Relative);

            pf = new PictureFrame();
            _pictures.Add(pf);
            pf.Source = new Uri("Images/Toco Toucan.jpg", UriKind.Relative);

            pf = new PictureFrame();
            _pictures.Add(pf);
            pf.Source = new Uri("Images/Tree.jpg", UriKind.Relative);

            pf = new PictureFrame();
            _pictures.Add(pf);
            pf.Source = new Uri("Images/Waterfall.jpg", UriKind.Relative);

            pf = new PictureFrame();
            _pictures.Add(pf);
            pf.Source = new Uri("Images/Winter Leaves.jpg", UriKind.Relative);

        }
    }
}

I think next step here it try the more efficient route so that I dont have to allocate all the PictureFrames together. But that means the API on the control will change. Maybe i will just try hosting videos together and see how that works.

Finally was able to upload ZIP to source file.

Visualization.zip

Advertisements
Standard

4 thoughts on “Stack3D Image Viewer

  1. Pingback: Recursive Reflection : Stack 3D Image Viewer

  2. Pingback: Post Bank Holiday Web Reading « Tales from a Trading Desk

  3. Pingback: Updated Stack 3D Image Viewer « Desperately Seeking Love of Sophie

  4. Pingback: Daniel Cernea » Blog Archive » Silverlight 1.1 – Installation and Testing » Daniel Cernea

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s