Click here to Skip to main content
15,867,488 members
Articles / Desktop Programming / WPF

XAML Graphics Series - Part 2 Silverlight 2.0 Desktop Art Animation

Rate me:
Please Sign up or sign in to vote.
3.77/5 (5 votes)
29 Jun 2009CPOL14 min read 36.5K   735   23   5
This is an introduction to creating XAML applications using Silverlight 2.0 and C#.
This Developer centered article with an index rating 5 out of 5

Article Index Rating:
Developer - Developer            (5) of 5      (Intermediate)
Architect - Architect             (2.86) of 5  (Beginner)
Graphic Designer - Graphic Designer   (2.38) of 5  (Beginner)

Image 5

Table of Contents

Introduction

   In the previous article in this series some goals were outlined.  The main goal of the series is to bridge the gap between all the stakeholders an application under development.  The stakeholders in this scenario are the employees of a low budget  startup company trying to raise capital thru various resources.  These resources are not in the scope of this article and the company has no management to report to.  Due to the limited funds our starving Artist, Developer, and Architect are utilizing rapid application development methodologies for their vapor wear product.  The products purpose is to create a paradigm shift for artists by creating interactive digital works of art for the internet community to consume.

  This series is divided up into sections for: Beginner (no programming experience needed), Intermediate (some programming knowledge helpful), and Advanced (advanced knowledge of programming concepts very helpful) audiences.

Break Down of Series:

  Beginner Series:
  1. Silverlight 1.0 Desktop Art Animation
  2. Silverlight 2.0 Desktop Art Animation
  3. XAML 2d Graphics Concepts
  4. XAML 3d Graphics Concepts

  Intermediate Series:
  5. Dynamic XAML
  6. WPF Application Design and Architecture
  7. Silverlight Application Design and Architecture
  8. Introduction to Advanced Concepts: DirectX, OpenGL, and Filtering

  Advanced Series:
  9. Anatomy of a GPU and how XAML is Optimized for GPU's
  10. GPU Optimization Techniques
  11. Mixing it all Up: DirectX, OpenGL, and More
  12. Bringing Everything Together, an Approach on Designing Custom Graphics Engines

Use Case - Lights! Camera! Action!

 Now the the stage is set for our actors the architecture of the prototype application can be discovered.  In a previous meeting of our actors they identified the graphic designer as the weakest link in their workflow.  Instead of out casting the weakest link 'as seen on TV' they decide to build the application around the designer as they have also identified their role as the principle for the application under development.  The designer tested the tools the company purchased for the development of this application and found that it was not easy to use, as some other tools are.  The designer's chief complaint was the inability to easily take the design and integrate it with the application.  The architect and developer sympathized with the designer and proposed a solution which would maximize the designers effectiveness in being a productive team member.  In this scenario the designer has a background in design and web development as a web master of a popular web site.  The team agrees upon investing resources to develop an in house shell based script utility the designer can use to rapidly integrate their designs into the prototype application base.

Architectural Design Rules

    Architectural Rules:
  • Code Behind Isolation - No code behind for base XAML (page, window) (host Layer)
  • Code Behind Isolation - Code behind preference on resource dictionaries (logic layer)
  • Code Behind Isolation - Code behind for user controls event routing preference (event routing)
  • Vectorized Image Isolation - Vectorized raster images will be isolated in separate resource dictionaries
  • Layer abstraction - Ability to define rules for all abstractions. (code policy enforcement)
  • Resource Identity - Resources will be strictly typed in XAML using the x:Type attribute
  • RAD - Tools for RAD will automate code and XAML stubs. Generators parse designer level XAML.
    Designer Rules:
  • Layer abstraction - Ability to graphically isolate custom code thru layer definition
  • Shell scripting - Interface for applying code generators via shell script
    Developer Rules:
  • Layer abstraction - Ability to convert XAML to resources.
  • Layer abstraction - Ability to define custom code transformations

Graphic Designer's Actions

Designe's Actions

Using the new rapid application tools tools developed for the company the designer begins to revise the original prototype.  The designer starts by defining layers overlaid on the image.  Each layer is then divvied into regions which define properties which will be associated to the graphic elements found in the region.  For example the designer wants to create an effect of the sun shining through a tree (not seen in the image) casting shadows and light on the vines growing on the side of the building.  The architect designed a simple xml parser which takes the name of the layer, and then locates all objects contained the the in each region of the layer.  The name of the layer tells the parser what effect to apply on the objects found by the code generator.  The designer is much relieved by the architect's innovation.  The designer also would like create shimmering effect(s) in various regions so a different layer is created and the developer works on designing the new effect.  Additionally, a prospective investor looked at the first prototype and thought it would be great if the next prototype demonstrated an interactive effect of ripples following the courser when the mouse scrolled across the art work so the designer creates two more layers for this effect: one to map the regions effected by this event, and another to define what effect will be applied for the event.

Developer's Actions

Developer's Actions

In this scenario the developer has the most work to do.  Not only has the team burdened the developer with creating a new tool, but the architect has also defined rules for the automatic code generation.  The developer agrees this is going to worth the effort and feels up to the challenge.  Unfortunately the developer quickly finds that this idea is so unique that he must write 80% of the code by hand and there are no other resources which the developer can utilize for this portion of the application other than the team.  The developer in this scenario was bought onto this project for his expertise in rapid application development techniques.  First the developer reviews the code guidelines and technologies which will be used in the prototype.  A pattern then starts to emerge.  After reading technical articles on the graphics package he realizes that identifying the objects in the framework can easily be found by trying to cast the reference object to the base type of the framework.  At this point the developer must make a decision about the usability of this method.  A simple test application is written using C#'s reflections class.  The UI assembly is opened using reflections, a for each loop is constructed for each class in the assembly.  The loop simply increments two counters: one for count all classes discovered by reflections, and another to record the hit count for classes which can be cast to the framework's base class.  Then the hit count is divided by class count.  This give the developer an approximation of the code coverage he can expect to be covered using this method.  Here is the listing for the test code and results:

/**
 * 
 * XAMLBox.Localizer Class
 * 
 * This class is used for testing the localization and isolation of Framework classes
 * 
 */
 
using System;
using System.Threading;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Windows;
 
namespace XAMLBox
{
    class Localizer
    {
        static void Main(string[] args)
        {
            //WPF requires the runtime to run as an STA thread..
            Thread t = new Thread(CastingHitCounter);
            t.SetApartmentState(ApartmentState.STA);
            t.Start();
            
 
        }
 
        private static void CastingHitCounter()
        {
            //Create an instance if a base class object in the framework
            FrameworkElement fe = new FrameworkElement();
 
            //Get a reference to the Type to reflect on
            Type t = Type.GetType(fe.GetType().AssemblyQualifiedName);
 
            //Get a reference to the assembly containing the Type 't' 
            //(System.Windows.FrameworkElement)
            Assembly FrameWork = Assembly.GetAssembly(t);
 
            //Create a box for our reflected Class to be used in a loop.
            Object RefClass = new Object();
 
            //Get all types defined in the assembly..
            Type[] TypeArr = FrameWork.GetTypes();
 
            //Total qualified types reported by the assembly...
            int qualifiedTypes = TypeArr.Length;
 
            //Total Class instances created..
            int classCount = 0;
 
            //Total bad reference counts..
            int badInstance = 0;
 
            //Total FrameworkElement casts..
            int feCount = 0;
 
            //Total FrameworkContentElement casts..
            int fceCount = 0;
 
            //Total Framework castable classes..
            int castCount = 0;
 
            //Total missed Framework counts..
            int missCount = 0;
 
            //Loop through each Type in the reflected assembly and count
            //casting hits.
            foreach (Type thisType in TypeArr)
            {
                try
                {
                    //Create an instance of a the reflected Type
                    RefClass = FrameWork.CreateInstance _ 
                          (thisType.AssemblyQualifiedName.ToString());
                    try
                    {
                        //Try to cast the reflected type to a FrameworkElement..
                        RefClass = (FrameworkElement)RefClass;
                        
                        //Increment our counters..
                        feCount++; 
                        castCount++;
 
                        //Try to cast the reflected type to a
                        //FrameworkContentElement..
                        RefClass = FrameWork.CreateInstance _
                           (//#thisType.AssemblyQualifiedName.ToString());
                        RefClass = (FrameworkContentElement)RefClass;
 
                        //Increment our counters..
                        fceCount++;
                        castCount++;
                    }
                    catch (Exception e)
                    {
                        try
                        {
                            //We missed a refrence of type FrameworkElement try 
                            //to cast it to a FrameworkContentElement..
                            RefClass = (FrameworkContentElement)RefClass;
 
                            //Incriment our counters..
                            fceCount++;
                            castCount++;
                        }
                        catch (Exception ex)
                        {
                            //Unable to cast to a Framework type, increment our 
                            //counter..
                            missCount++;
                        }
                    }
                    finally
                    {
                        //Clean up our box..
                        RefClass = null;
 
                        //Incriment Class counter..
                        classCount++;
                    }
                }
                catch (Exception e)
                {
                    //Incriment Class counter..
                    classCount++;
 
                    //This is not an instance class, increment the counter..
                    badInstance++;
                }
            }
 
            //Report the statistics:
            Console.Out.WriteLine("Total Qualified Types: " + _
                                  qualifiedTypes);
            Console.Out.WriteLine("Total Qualified Classes: " + _
                                  classCount);
            Console.Out.WriteLine("Total Bad Instances: " + _
                                  badInstance);
            Console.Out.WriteLine("Total Workable FrameWork Types: " _
                                  + castCount);
            Console.Out.WriteLine("Total FrameworkElements: " _
                                  + feCount);
            Console.Out.WriteLine("Total FrameworkContentElements: " _
                                  + fceCount);
            Console.Out.WriteLine("Total Missed Types: " + missCount);
            Console.Out.WriteLine("Qualified Type to Missed Type Ratio: " _
                                  + //#((missCount / qualifiedTypes) * 100));
            Console.Out.WriteLine("FrameworkElement to Qualified Class Ratio: " _
                                  + //#((feCount / classCount) * 100));
            Console.Out.WriteLine("FrameworkContentElement to Qualified Class Ratio: " _
                                  + ((fceCount / classCount) * 100));
            Console.Out.WriteLine("Bad Instance to Qualified Class Ratio: " + _
                                 ((badInstance / classCount) * 100));
            Console.Out.WriteLine("Error Ratio: " + _
                              (((badInstance + missCount / classCount) * 100));
 
            //Set breakpoint here to view the results..
            Console.Out.WriteLine("End of Report");
        }
    }
}

Output of localizer test tool:
Total Qualified Types: 2336
Total Qualified Classes: 2336
Total Bad Instances: 0
Total Workable FrameWork Types: 4672
Total FrameworkElements: 2336
Total FrameworkContentElements: 2336
Total Missed Types: 0


Qualified Type to Missed Type Ratio: 0
FrameworkElement to Qualified Class Ratio: 100
FrameworkContentElement to Qualified Class Ratio: 100

Bad Instance to Qualified Class Ratio: 0
Error Ratio: 0

  After reviewing the results of the localizer tool, the developer decides this correct course of action and the architect agrees.  After a code review by the architect, errors are found and corrected.  Although the results seem to fit the frameworks design pattern it is a little suspicious that there is a 100% correlation to the base reflected class to all classes in the framework.  This can be a common mistake of over ambitious development cycles.  Here are the results after the defects are corrected:

Total Qualified Types: 2336
Total Qualified Classes: 2336
Total Cast able FrameWork Types: 131
Total Cast able FrameworkElements: 105
Total Cast able FrameworkContentElements: 26
Total Unique Base Classes: 306

Total Missed Types: 883
Total Bad Instances: 1427

Total Direct Descendants of FrameworkElement: 0
Total Direct Descendants of FrameworkContentElement: 0
Total Ancestors of FrameworkElement: 145
Total Ancestors of FrameworkContentElement: 30
Total Sealed Classes: 1112
Total Abstract Classes: 288

Qualified Type to Missed Type Ratio: 37.7996575342466
FrameworkElement to Qualified Class Ratio: 4.49486301369863
FrameworkContentElement to Qualified Class Ratio: 1.11301369863014
Bad Instance to Qualified Class Ratio: 61.0873287671233

Unique Base Type to Qualified Class Ratio: 13.0993150684931

Direct Descendants of FrameworkElement to Class Count: 0
Direct Descendants of FrameworkContentElement to Class Count: 0
Direct Descendants of FrameworkElement to Unique Base Types: 0
Direct Descendants of FrameworkContentElement to Unique Base Types: 0

Ancestors of FrameworkElement to Class Count: 6.20719178082192
Ancestors of FrameworkContentElement to Class Count: 1.28424657534247
Ancestors of FrameworkElement to Unique Base Types: 47.3856209150327
Ancestors of FrameworkContentElement to Unique Base Types: 9.80392156862745

Error Ratio: 98.8869863013699

  Interpretation of the results can be a little tricky.  At first glance an error ratio of 98% seems to indicate a bad correlation.  However a combined hit ratio of 57% on base types indicates that this is a good entry point into this frame work.  Documentation on the framework confirms this indicator.  However the absence of direct descendants is still suspicious as at least one class in the framework must directly derive from the reflected classes unless they share a common interface.  The documentation indicates that the classes used in this test are not good candidates for use as base classes and that higher level entry points should be used for deriving new classes.  This point is mute for the intended use of these classes.  The process of generating code automatically using code generation or Domain Specific Languages simplified by using low-level API's which have a common ancestor or interface.  The ability to polymorph a high-level API class to a common type simplifies the amount of custom code branches with out sacrificing any customized high-level implementations.  Understanding the difference in implementation of inheritance based on an common inter face and common ancestor is necessary as casting to a base ancestor class would reduce the implementation to it's lowest level implementation and would looses any customizations applied by higher level classes.  There is much ambiguity between either implementation style as each can be implemented in such a way as to add value to the framework.  It is more a choice of design and architecture as to how the low-level API are designed.  Both are useful alone or working in conjunction if the design is implemented correctly.  A balance on simplicity and usability is the common preference of good OO designs.

   Upon investigation of the ratios calculated for "Direct Descendants" reveals further error in the code.  The following modifications were made for calculating direct descendants:

 if (BaseType.FullName.ToString() == "System.Windows.FrameworkElement")    fwDdCount++;<br /><br /><br /> if (BaseType.FullName.ToString() == "System.Windows.FrameworkContentElement")    fwcDdCount++;

The ratio results after modification:
Direct Descendants of FrameworkElement to Class Count: 1.41267123287671
Direct Descendants of FrameworkContentElement to Class Count: 0.256849315068493
Direct Descendants of FrameworkElement to Unique Base Types: 10.7843137254902
Direct Descendants of FrameworkContentElement to Unique Base Types: 1.96078431372549

  From these results can branching ratios can calculated:
    (fwDdCount/fwAnsestorCount) * 100 = 23%
    (fwcDdCount/fwcAnsestorCount) * 100 = 20%

Almost equal.

  After calculating the branching of the framework some proto types need to be created to get a performance result of the effects which are to be used.  The first two effects are the sparkle and sun light effects.  The proto type uses an editable image buffer to render the effects and then streams them to the browser via a PNG encoder.  The effects are displayed using an ImageBrush an alternate method would be to recode and encode the effect and then display using a VideoBrush.  The alternate will be explored in the next article in this series.  A test application is created with a XAML test page and a SparkleFilter class.  The sparkle filter uses the EditableImage and PngEncode classes to render the filter animation.  Some modifications were made to the editable image class to help performance and extend some functionality like alpha blending.

  The test runs at 100% CPU on an Intel Pentium 4 Willamette running at 1.76Ghz at 20 frames per second.  50% at 18 fps, and 7% at 10 fps.  Since the target image to be using in this prototype has over 4000 image elements to be animated an alternate method or more performance modifications must be made to allow the animation to run smoothly at 20 fps.

Architect's ActionsMock Code Review

Architect's Actions

The architect first reviews the technology of many designer applications, file formats, code restrictions, security considerations and integration artifacts.  A matrix of is created for all artifacts discovered.  The list is then indexed by: usability, code ability, and isolation level.  Each index is weighed on a scale of one to five.  One signifying not relevant five signifying core reliance.  Once the matrix is complete the index value for each artifact is calculated as a ratio.  The ratio index for each artifact above 3 is then defined as a rule the architecture must follow.  Artifacts are discovered based on business decisions, team feedback, and the architect's knowledge and experience of designing reusable code.

  After looking over the code the architect notices that the developer made some clumsey mistakes.  First mistake is that the code does not correctly dynamically generate the reference class.  The result is that the reflections code generated the same class over and over agian.  The second mistake is using int(s) for precision math.  Ratios can not be correctly caculated unless floating point precision is used.  The architect also added more code to further understand the dependancies and relationships of the classes in the assembly.  Here is the corrected listing:

/**
 * 
 * XAMLBox.Localizer Class
 * 
 * This class is used for testing the localization and isolation of Framework classes
 * 
 */
 
using System;using System.Threading;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Windows;
 
namespace XAMLBox
{
    
    class Localizer
    {
        static Thread STAThread = new Thread(CastingHitCounter);
        static void Main(string[] args)
        {
 
            STAThread.SetApartmentState(ApartmentState.STA);
            STAThread.Start();
 
        }
 
        private static void CastingHitCounter()
        {
            
            //Create an instance if a base class object in the framwork
            FrameworkElement fe = new FrameworkElement();
            FrameworkContentElement fce = new FrameworkContentElement();
 
            //Get a refrence to the Type to reflect on
            Type t = Type.GetType(fe.GetType().AssemblyQualifiedName);
 
            //Get a refrence to the assembly containing the Type 't' (System.Windows.FrameworkElement)
            Assembly FrameWork = Assembly.GetAssembly(t);
 
            //Create a box for our reflected Class to be used in a loop.
            Object RefClass = new Object();
 
            //Create a box for our reflected Class to be used in a loop.
            Type BaseType;
 
            //Get all types defined in the assembly..
            Type[] TypeArr = FrameWork.GetTypes();
 
            //List of unique base types..
            ArrayList UBaseTypes = new ArrayList(250);
 
            //Total qualified types reported by the assembly...
            double qualifiedTypes = TypeArr.Length;
 
            //Total Class instances created..
            double classCount = 0;
 
            //Total unique base types..
            double baseTypeCount = 0;
 
            //Total Direct FrameworkElement Decendants..
            double fwDdCount = 0;
 
            //Total Direct FrameworkContentElement Decendants..
            double fwcDdCount = 0;
 
            //Total anserstoral FrameworkElements..
            double fwAnsestorCount = 0;
 
            //Total anserstoral FrameworkContentElements..
            double fwcAnsestorCount = 0;
 
            //Sealed Classes..
            double sealedClasses = 0;
 
            //Abstract Classes..
            double abstractClasses = 0;
 
            //Total bad refrence counts..
            double badInstance = 0;
 
            //Total FrameworkElement casts..
            double feCount = 0;
 
            //Total FrameworkContentElement casts..
            double fceCount = 0;
 
            //Total Framework castable classes..
            double castCount = 0;
 
            //Total missed Framework counts..
            double missCount = 0;
 
            //Loop through each Type in the reflected assembly and count casting hits.
            foreach (Type thisType in TypeArr)
            {
                try
                {
                    //Discover ansestory..
                    BaseType = thisType.BaseType;
                    if (UBaseTypes.IndexOf(BaseType) == -1)
                    {
                        UBaseTypes.Add(BaseType);
                        baseTypeCount++;
                    }
 
                    if (thisType.FullName.ToString() == "System.Windows.FrameworkElement")
                        fwDdCount++;
 
                    if (thisType.FullName.ToString() == "System.Windows.FrameworkContentElement")
                        fwcDdCount++;
 
                    if (thisType.IsSubclassOf(fe.GetType()))
                        fwAnsestorCount++;
 
                    if (thisType.IsSubclassOf(fce.GetType()))
                        fwcAnsestorCount++;
 
                    if (thisType.IsSealed)
                        sealedClasses++;
 
                    if (thisType.IsAbstract)
                        abstractClasses++;
 
                    //Create an instance of a the reflected Type
                    Thread.BeginCriticalRegion();
                    RefClass = FrameWork.CreateInstance(thisType.FullName.ToString());
                    
                    try
                    {
                        //Try to cast the reflected type to a FrameworkElement..
                        RefClass = (FrameworkElement)RefClass;
                        
                        //Incriment our counters..
                        feCount++; 
                        castCount++;
 
                        //Try to cast the reflected type to a FrameworkContentElement..
                        RefClass = FrameWork.CreateInstance(thisType.FullName.ToString());
                        RefClass = (FrameworkContentElement)RefClass;
 
                        //Incriment our counters..
                        fceCount++;
                        castCount++;
                    }
                    catch (Exception e)
                    {
                        try
                        {
                            //We missed a reference of type FrameworkElement try to cast it to a FrameworkContentElement..
                            RefClass = (FrameworkContentElement)RefClass;
 
                            //Increment our counters..
                            fceCount++;
                            castCount++;
                        }
                        catch (Exception ex)
                        {
                            //Unable to cast to a Framework type, increment our counter..
                            missCount++;
                        }
                    }
                    finally
                    {
                        //Clean up our box..
                        RefClass = null;
 
                        //Increment Class counter..
                        classCount++;
                    }
                    Thread.EndCriticalRegion();
                }
                catch (Exception e)
                {
                    //Increment Class counter..
                    classCount++;
 
                    //This is not an instance class, increment the counter..
                    badInstance++;
                }
            }
 
            //Report the statistics:
            Console.Out.WriteLine("Total Qualified Types: " + qualifiedTypes);
            Console.Out.WriteLine("Total Qualified Classes: " + classCount);
            Console.Out.WriteLine("Total Cast able FrameWork Types: " + castCount);
            Console.Out.WriteLine("Total Cast able FrameworkElements: " + feCount);
            Console.Out.WriteLine("Total Cast able FrameworkContentElements: " + fceCount);
            Console.Out.WriteLine("Total Unique Base Classes: " + UBaseTypes.Count);
 
            Console.Out.WriteLine("\nTotal Missed Types: " + missCount);
            Console.Out.WriteLine("Total Bad Instances: " + badInstance);
 
            Console.Out.WriteLine("\nTotal Direct Decendants of FrameworkElement: " + fwDdCount);
            Console.Out.WriteLine("Total Direct Decendants of FrameworkContentElement: " + fwcDdCount);
            Console.Out.WriteLine("Total Ansestors of FrameworkElement: " + fwAnsestorCount);
            Console.Out.WriteLine("Total Ansestors of FrameworkContentElement: " + fwcAnsestorCount);
            Console.Out.WriteLine("Total Sealed Classes: " + sealedClasses);
            Console.Out.WriteLine("Total Abstract Classes: " + abstractClasses);
            
            Console.Out.WriteLine("\nQualified Type to Missed Type Ratio: " + ((missCount / qualifiedTypes) * 100));
            Console.Out.WriteLine("FrameworkElement to Qualified Class Ratio: " + ((feCount / classCount) * 100));
            Console.Out.WriteLine("FrameworkContentElement to Qualified Class Ratio: " + ((fceCount / classCount) * 100));
            Console.Out.WriteLine("Bad Instance to Qualified Class Ratio: " + ((badInstance / classCount) * 100));
 
            Console.Out.WriteLine("\nUnique Base Type to Qualified Class Ratio: " + ((UBaseTypes.Count / classCount) * 100));
            
            Console.Out.WriteLine("\nDirect Decendants of FrameworkElement to ClassCount: " + ((fwDdCount / classCount) * 100));
            Console.Out.WriteLine("Direct Decendants of FrameworkContentElement to ClassCount: " + ((fwcDdCount / classCount) * 100));
            Console.Out.WriteLine("Direct Decendants of FrameworkElement to Unique Base Types: " + ((fwDdCount / UBaseTypes.Count) * 100));
            Console.Out.WriteLine("Direct Decendants of FrameworkContentElement to Unique Base Types: " + ((fwcDdCount / UBaseTypes.Count) * 100));
            
            Console.Out.WriteLine("\nAnsestors of FrameworkElement to ClassCount: " + ((fwAnsestorCount / classCount) * 100));
            Console.Out.WriteLine("Ansestors of FrameworkContentElement to ClassCount: " + ((fwcAnsestorCount / classCount) * 100));
            Console.Out.WriteLine("Ansestors of FrameworkElement to Unique Base Types: " + ((fwAnsestorCount / UBaseTypes.Count) * 100));
            Console.Out.WriteLine("Ansestors of FrameworkContentElement to Unique Base Types: " + ((fwcAnsestorCount / UBaseTypes.Count) * 100));
 
            Console.Out.WriteLine("\nError Ratio: " + (((badInstance + missCount) / classCount) * 100));
                //Set brekpodouble here to view the results..
            Console.Out.WriteLine("End of Report");
        }
    }
}

 The use of the sparkle effect caused many problems for performance.  However using Silverlight it is not possible to sample any of the colors in the generated image the browser displays.  So it was necessary to create a special filter using Joe Stegman's EditableImage and PngEncode classes.  Link to Joe's WebBlog.  The SparkleFilter class uses 4 image buffers for image processing: AlphaBuffer, SparkleBuffer, Back Buffer, and Cache Buffer.  The alpha buffer contains the instructions for applying an alpha filter, the sparkle buffer contains instructions for applying luminance factors, the back buffer is where the animation is rendered and the cache buffer contains a copy of the original image which is copied to the back buffer at the beginning of each frame to refresh the source image.  A Dictionary<Color, EditableImage> is used to cache composite sparkle buffers for the same colors.  Here is the code for test page:

<UserControl Name="UserControl1" x:Class="SilverlightTestApp.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width="500" Height="500" Loaded="UserControl_Loaded">
    <UserControl.Resources>
        <SolidColorBrush x:Key="GreenBrush" Color="#FF00FF00"/>
        <ImageBrush x:Key="IBrushRandom" ImageFailed="ImageBrush_ImageFailed" />
        <SolidColorBrush x:Key="IBrushSparkle1" Color="Red" />
		<SolidColorBrush x:Key="IBrushSparkle2" Color="Blue" />
		<SolidColorBrush x:Key="IBrushSparkle3" Color="Green" />
		<SolidColorBrush x:Key="IBrushSparkle4" Color="#70ff0000" />
		<SolidColorBrush x:Key="IBrushSparkle5" Color="#7000ff00" />
		<SolidColorBrush x:Key="IBrushSparkle6" Color="#700000ff" />
    </UserControl.Resources>

    <Canvas Height="500" Width="500">
		<Rectangle Height="500" Width="500" Fill="#FF000000" Stroke="#FF000000"/>
		<Grid Height="500" Width="500">
			<StackPanel Margin="3,3,0,0">
				<TextBlock x:Name="GreenFade"
					Canvas.Left="5" Canvas.Top="200"  
					FontFamily="Verdana" FontSize="70" FontWeight="Bold" 
					Text="Green Fade" Foreground="{StaticResource GreenBrush}"/>
			    
				<TextBlock x:Name="RandomPixels"
					Canvas.Left="5" Canvas.Top="200"  
					FontFamily="Verdana" FontSize="50" FontWeight="Bold" 
					Text="Random Pixels" Foreground="{StaticResource IBrushRandom}"/>
		
				<TextBlock x:Name="SparkleText1"
					Canvas.Left="5" Canvas.Top="200"  
					FontFamily="Verdana" FontSize="20" FontWeight="Bold" 
					Text="Sparkle" Foreground="{StaticResource IBrushSparkle1}"/>
				
				<TextBlock x:Name="SparkleText2"
					Canvas.Left="5" Canvas.Top="200"  
					FontFamily="Verdana" FontSize="40" FontWeight="Bold" 
					Text="Sparkle" Foreground="{StaticResource IBrushSparkle2}"/>
				
				<TextBlock x:Name="SparkleText3"
					Canvas.Left="5" Canvas.Top="200"  
					FontFamily="Verdana" FontSize="80" FontWeight="Bold" 
					Text="Sparkle" Foreground="{StaticResource IBrushSparkle3}"/>
			</StackPanel>
			<Canvas Height="174" VerticalAlignment="Bottom">
				<Rectangle Height="174" Width="500" Stroke="#FF000000">
					<Rectangle.Fill>
						<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
							<GradientStop Color="#FF000000"/>
							<GradientStop Color="#FFFFFFFF" Offset="1"/>
						</LinearGradientBrush>
					</Rectangle.Fill>
				</Rectangle>
				<Canvas Canvas.Top="35" Canvas.Left="145" Height="154" Width="190">
					<Ellipse Name="BlueCircle" Canvas.Top="45" Canvas.Left="80" Height="90" Width="90" Fill="{StaticResource IBrushSparkle6}"/>
					<Ellipse Name="GreenCircle" Canvas.Top="45" Canvas.Left="20" Height="90" Width="90" Fill="{StaticResource IBrushSparkle5}"/>
					<Ellipse Name="RedCircle" Canvas.Left="50" Height="90" Width="90" Fill="{StaticResource IBrushSparkle4}"/>
				</Canvas>
			</Canvas>
		</Grid>

	</Canvas>

</UserControl>
C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

using System.Windows.Threading;
using System.Windows.Browser;

using Silverlight.Samples;
namespace SilverlightTestApp
{
    public partial class Page : UserControl
    {
        private EditableImage image;

        public Page()
        {
            InitializeComponent();
        }

        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {
            // Set timer for changing the first image
            DispatcherTimer   dt = new DispatcherTimer();

            //Set Sparkle Filters
            SparkleFilter sf1 = new SparkleFilter(SparkleText1, "Foreground", null);
            SparkleFilter sf2 = new SparkleFilter(SparkleText2, "Foreground", null);
            SparkleFilter sf3 = new SparkleFilter(SparkleText3, "Foreground", null);
            SparkleFilter sf4 = new SparkleFilter(RedCircle, "Fill", null);
            SparkleFilter sf5 = new SparkleFilter(GreenCircle, "Fill", null);
            SparkleFilter sf6 = new SparkleFilter(BlueCircle, "Fill", null);

            //Set Main Timer Intrerval
            dt.Interval = new TimeSpan(0, 0, 0, 0, 100);

            //Add all event handlers for timers
            dt.Tick += new EventHandler(dt_Tick1);
            dt.Tick += new EventHandler(dt_Tick2);
            dt.Tick += new EventHandler(sf1.TimerTick);
            dt.Tick += new EventHandler(sf2.TimerTick);
            dt.Tick += new EventHandler(sf3.TimerTick);
            dt.Tick += new EventHandler(sf4.TimerTick);
            dt.Tick += new EventHandler(sf5.TimerTick);
            dt.Tick += new EventHandler(sf6.TimerTick);

            //Set dispatch timer for each Sparkle filter
            sf1.Timer = dt;
            sf2.Timer = dt;
            sf3.Timer = dt;
            sf4.Timer = dt;
            sf5.Timer = dt;
            sf6.Timer = dt;

            // Start the timer, only need to start the first one since they all share the same dispatcher.
            sf1.StartDispatcher();
        }

        bool peak = true;
        byte b;
        void dt_Tick1(object sender, EventArgs e)
        {
            if (((SolidColorBrush)this.GreenFade.Foreground).Color.G == 255) peak = true;
            else if (((SolidColorBrush)this.GreenFade.Foreground).Color.G == 0) peak = false;

            b = ((SolidColorBrush)this.GreenFade.Foreground).Color.G;

            if (peak)
                ((SolidColorBrush)this.GreenFade.Foreground).Color = Color.FromArgb(255, 0, --b, 0);
            else ((SolidColorBrush)this.GreenFade.Foreground).Color = Color.FromArgb(255, 0, ++b, 0); ;
        }

        void dt_Tick2(object sender, EventArgs e)
        {
            BitmapImage bm = new BitmapImage();
            Random rand = new Random();
            int height = 128;
            int width = 128;
            bool first = true;

            // Re-use a single EditableImage
            if (null == image)
            {
                image = new EditableImage(height, width);
            }

            // Generate a random image that changes every tick
            for (int idx = 0; idx < height; idx++)      // Height (y)
            {
                for (int jdx = 0; jdx < width; jdx++)     // Width (x)
                {
                    image.SetPixel(jdx, idx, (byte)rand.Next(255), (byte)rand.Next(255), (byte)rand.Next(255), 255);
                }
            }

            // Get stream and set image source
            bm.SetSource(image.GetStream);
            RandomPixels.Foreground = new ImageBrush();

            ((ImageBrush)RandomPixels.Foreground).Stretch = System.Windows.Media.Stretch.UniformToFill;
            ((ImageBrush)RandomPixels.Foreground).ImageSource = bm;

            if (first)
            {
                //img.InvalidateMeasure();
                first = false;
            }
        }

        private void ImageBrush_ImageFailed(object sender, ExceptionRoutedEventArgs e)
        {
            HtmlPage.Window.Alert(e.ErrorException.Message);
        }

    }
}
C#
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

using System.Windows.Threading;
using System.Windows.Browser;

using Silverlight.Samples;
namespace SilverlightTestApp
{
    public class SparkleFilter
    {
        private EditableImage _ei;
        private EditableImage _eiCache;
        private EditableImage _backBuffer;
        private BitmapImage _bm;
        private Dictionary<color, /> ColorMap = new Dictionary<color, />();

        public SparkleFilter(FrameworkElement ReferenceElement, String ReferenceProperty, EditableImage ImageIn)
        {
            try
            {
                _backBuffer = new EditableImage(5, 5);
                this.Stretch = Stretch.None;

                this.Brush = new ImageBrush();
                ReferenceColor = ((SolidColorBrush)ReferenceElement.GetType().GetProperty(ReferenceProperty).GetValue(ReferenceElement,null)).Color;
                ReferenceElement.GetType().GetProperty(ReferenceProperty).SetValue(ReferenceElement,this.Brush, null);
            }
            catch (Exception Ex)
            {
                throw new ArgumentException("Reference object's refrence property not supported.", Ex);
            }

            try
            {
                this.ReferenceImageSize = new Rect(0, 0,((double)ReferenceElement.GetType().GetProperty("ActualWidth").GetValue(ReferenceElement, null)), ((double)ReferenceElement.GetType().GetProperty("ActualHeight").GetValue(ReferenceElement, null)));
                //this.ReferenceImageSize.Height = ((double)ReferenceElement.GetType().GetProperty("ActualHeight").GetValue(ReferenceElement, null));
                //this.ReferenceImageSize.Width = ((double)ReferenceElement.GetType().GetProperty("ActualWidth").GetValue(ReferenceElement, null));
            }
            catch (Exception Ex)
            {
                throw new ArgumentException("Reference object has no Geometry!", Ex);
            }

            if (ImageIn != null)
                _ei = ImageIn;

            this.SampleReference = true;
            this.ReferenceElement = ReferenceElement;
            this.ImageIn = ImageIn;
            Sparkle();
        }

        //set the Timer.
        public DispatcherTimer Timer {get; set;}

        //This is the FrameworkElement the effect is applied to.
        public FrameworkElement ReferenceElement { get; set; }

        public EditableImage ImageIn
        {
            get { return _ei; }
            set { _ei = value; Sparkle(); }
        }

        public EditableImage ImageOut
        {
            get { return _ei; }
        }

        public BitmapImage ImageSource
        {
            get { return _bm; }
        }


        //ReferenceColor the color of the ReferenceElement.
        public Color ReferenceColor { get; set; }

        //Send the Geometry.Bounds in as size
        public Rect ReferenceImageSize { get; set; }

        //distance between Sparkles
        public bool SparkleDeltaBasedOnImageSize { get;  set; }
        
        //global pixel modifier, modifies luminance gausienblur.
        public int SparkleWeight { get; set; }

        private int[][] Sparklebuffer = new int[5][];
        //5x5 byte pixel array used to set a custom Sparkle patern, 
        //byte used as a weight to modify sample color.
        public int[][] SparkleBuffer { get { return Sparklebuffer; } set { Sparklebuffer = value; } }

        private byte[][] alphabuffer = new byte[5][];
        //5x5 byte pixel array used to set a custom Sparkle patern, 
        //byte used to modify alpha component of color.
        public byte[][] AlphaBuffer { get { return alphabuffer; } set { alphabuffer = value; } }

        //Set to tru to sample the reference object's color.  Otherwise sample the buffer.
        public bool SampleReference { get; set; }

        //Set to true if the filter is still rendering.
        public bool IsRendering { get; set; }

        public void StartDispatcher() { Timer.Start(); }
        public void StopDispatcher() { Timer.Stop(); }

        //Defaults to 20 frames per second.  100 milli seconds.
        public TimeSpan TimeIntrival { get; set; }

        public ImageBrush Brush { get; set; }

        //Fill mode.
        public Stretch Stretch { get; set; }

        public void Sparkle()
        {
            
            // Set timer for changing the first image
            if (this.Timer == null)
            {
                this.Timer = new DispatcherTimer();
                this.Timer.Interval = new TimeSpan(0, 0, 0, 0, 100);
            }
            if (this.TimeIntrival == null)
                this.TimeIntrival = Timer.Interval;


            if (SparkleWeight == 0)
                SparkleWeight = 10;

            if (Sparklebuffer[0] == null)
            {
                Sparklebuffer = new int[5][];
                Sparklebuffer[0] = new int[] { 25,  50,  85,  50,  25 };
                Sparklebuffer[1] = new int[] { 50,  85, 125,  85,  50 };
                Sparklebuffer[2] = new int[] { 85, 125, 150, 125,  85 };
                Sparklebuffer[3] = new int[] { 50,  85, 125,  85,  50 };
                Sparklebuffer[4] = new int[] { 25,  50,  85,  50,  25 };
            }

            if (alphabuffer[0] == null)
            {
                alphabuffer = new byte[5][];
                alphabuffer[0] = new byte[] {  20, 100, 130, 100,  20 };
                alphabuffer[1] = new byte[] { 100, 130, 200, 130, 100 };
                alphabuffer[2] = new byte[] { 130, 200, 250, 200, 130 };
                alphabuffer[3] = new byte[] { 100, 130, 200, 130, 100 };
                alphabuffer[4] = new byte[] {  20, 100, 130, 100,  20 };
            }

            Timer.Tick += new EventHandler(TimerTick);
        }


        /*
         * RGB to YUV LuminanceTransform
         */
        private static Color LuminanceTransform(Color SampleColor, int Weight, byte Alpha)
        {
            byte Y, U, V;

            Y = (byte)(((66 * SampleColor.R + 129 * SampleColor.G + 25 * SampleColor.B + 128) >> 8) + 16);
            U = (byte)(((-38 * SampleColor.R - 74 * SampleColor.G + 112 * SampleColor.B + 128) >> 8) + 128);
            V = (byte)(((112 * SampleColor.R - 94 * SampleColor.G - 18 * SampleColor.B + 128) >> 8) + 128);


            Y = (byte)Math.Min((Y+Weight), 255);

            int C = Y - 16;
            int D = U - 128;
            int E = V - 128;


            SampleColor.R = (byte)Math.Max(Math.Min(((298 * C + 409 * E + 128) >> 8), 255), 0);
            SampleColor.G = (byte)Math.Max(Math.Min(((298 * C - 100 * D - 208 * E + 128) >> 8), 255), 0);
            SampleColor.B = (byte)Math.Max(Math.Min(((298 * C + 516 * D + 128) >> 8), 255), 0);
            SampleColor.A = Alpha;

            return SampleColor;
        }

        internal void TimerTick(object sender, EventArgs e)
        {
            this.IsRendering = true;
            this.Timer.Stop();

            _bm = new BitmapImage();
            Random rand = new Random();

            int height = 128;
            int width = 128;

            //Re-use a single EditableImage
            if (_ei == null)
            {
                width = Math.Min(128,Convert.ToInt32(ReferenceImageSize.Width));
                height = Math.Min(128,Convert.ToInt32(ReferenceImageSize.Height));

                if (width == 128 || height == 128)
                    Stretch = Stretch.UniformToFill;

                _ei = new EditableImage(width, height);
                
                for (int idx = 0; idx < height; idx++)      // Height (y)
                {
                    for (int jdx = 0; jdx < width; jdx++)     // Width (x)
                    {
                        _ei.SetPixel(jdx, idx, ReferenceColor.R, ReferenceColor.G, ReferenceColor.B, ReferenceColor.A);
                    }
                }

                //Cache the origional image
                _eiCache = new EditableImage(_ei.Width,_ei.Height);
                _eiCache.SetPixels(_ei);
            }

            int density = (Convert.ToInt32(ReferenceImageSize.Width) + Convert.ToInt32(ReferenceImageSize.Height))/2;
            Color SampleColor;
            int sampleY;
            int sampleX;
            //Replace image buffer with cache.
            _ei.SetPixels(_eiCache);
            // Generate a random image that changes every tick
            for (int sp = 0; sp < density; sp++)      
            {

                sampleY = rand.Next(_ei.Height - 5); //Have to pad, 5 pixels on both sides.
                sampleX = rand.Next(_ei.Width - 5);
                
                if (SampleReference)
                    SampleColor = ReferenceColor;
                else
                    SampleColor = _ei.GetPixel(sampleY,sampleX);

                if (!ColorMap.ContainsKey(SampleColor))
                {
                    for (int y = 0; y < 5; y++)
                    {
                        for (int x = 0; x < 5; x++)
                        {
                            Color c = LuminanceTransform(SampleColor, Sparklebuffer[y][x], alphabuffer[y][x]);
                            _backBuffer.SetPixel(x, y, c);

                        }
                    }
                    ColorMap.Add(SampleColor, _backBuffer);
                }
                else
                {
                    _backBuffer = ColorMap[SampleColor];
                }
                
                for (int idx = sampleY, y1 = 0; idx < (_backBuffer.Height + sampleY); idx++, y1++)   
                {
                    for (int jdx = sampleX, x1 = 0; jdx < (_backBuffer.Width + sampleX); jdx++, x1++)
                    {
                        _ei.BlendAlphaPixel(jdx, idx, _backBuffer.GetPixel(y1, x1));
                    }
                }
            }

            // Get stream and set image source
            _bm.SetSource(_ei.GetStream);
            ((ImageBrush)this.Brush).Stretch = this.Stretch;
            ((ImageBrush)this.Brush).ImageSource = _bm;

            this.Timer.Start();
            this.IsRendering = false;
        }

    }
}
 That's it! 

Lessons Learned

 It is important to use caution when using Reflection's in C#.  Many errors can easily be made and security precautions must be taken to assure that malicious code can not inject method calls.  This is best accomplished by limiting the accessibility of the methods containing reflection code with internal, private, or protected access.

Points of Interest

   Silverlight does not allow sampling of rendered content.  Additionally all elements are re-blended when new content is added, this is avoided in this example using the Joe Stegman's EditableImage class.

External Links:

XAML Overlay Prototype Research

Graphic Designer - Graphic Designer (2.38) Developer - Developer (5) Architect - Architect (2.86)

Revision History:

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
CEO AW Proto-Code, Inc.
United States United States
I’m a self learned wiz kid turned Architect. Stared with an Apple IIe, using AppleSoft Basic, ProDos and Begal Basic at age 10.

Picked up LPC in college before the Dot Com Explosion. Wrote some Object C in the USAF for one of my instructors. Got a break from a booming computer manufacture testing server software. Left the Boom and went to work for a Dot Com built from the ashes of Sun Micro in CS. Mentoring in Java solidified me as a professional developer. Danced in the light of the sun for 13 years, before turning to the dark side. An evil MVP mentored me in the ways of C# .NET. I have not looked back since.

Interests include:

~ Windows Presentation Foundation and Silverlight
~ Parallel Programming
~ Instruction Set Simulation and Visualization
~ CPU to GPU code conversion
~ Performance Optimizations
~ Mathematics and Number Theory
~ Domain Specific Languages
~ Virtual Machine Design and Optimization
~ Graphics Development
~ Compiler Theory and Assembler Conversion Methodology
~ CUDA, OpenCL, Direct Compute, Quantum Mechanics

IEEE Associate Member 2000
This is a Organisation (No members)


Comments and Discussions

 
NewsFixed Missing projects Pin
ProtoBytes29-Jun-09 5:52
ProtoBytes29-Jun-09 5:52 
GeneralMissing Project Pin
Viral Upadhyay28-Jun-09 20:34
Viral Upadhyay28-Jun-09 20:34 
AnswerRe: Missing Project Pin
ProtoBytes29-Jun-09 2:54
ProtoBytes29-Jun-09 2:54 
News[Message Deleted] Pin
ProtoBytes23-Jun-09 1:29
ProtoBytes23-Jun-09 1:29 
News[Message Deleted] Pin
ProtoBytes28-Jun-09 16:04
ProtoBytes28-Jun-09 16:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.