Silverlight Stopwatch Class, Part 2

I recently made a fix to a Silverlight stopwatch class I wrote last year.

In doing so, I decided to kick it up a notch.

using (new StopwatchPlus("Constructor"))
{
    //Debug.WriteLine("Initialized: Ellapsed Ticks: {0}, Ellapsed Milliseconds: {1}", sw.EllapsedTicks, sw.EllapsedMilliseconds); InitializeComponent();
    Thread.Sleep(600);            
}

Instead of just a simple Stopwatch class, I added functionality to make it easy to capture timings for blocks of code, output to the Debug Output window, and perform any custom start/stop action.

 

StopwatchPlus sp1 = new StopwatchPlus(sw2 => 
        Debug.WriteLine("Time! {0}", sw2.EllapsedMilliseconds));
Thread.Sleep(500);
sp1.Stop();    // this will call the stopAction defined in the constructor 

 

In non-debug builds, the StopwatchPlus class does not send output to the debug window. (I used the ConditionalAttribute … I know I could have used conditional compilation statements, but I didn’t. :-) ).

using System;
using System.Diagnostics;

namespace WiredPrairie.Silverlight
{
    /// <summary> /// StopwatchPlus is used to measure the general performance of Silverlight functionality. Silverlight /// does not provide a high resolution timer as is available in many operating systems, /// so the resolution of this timer is limited to milliseconds. This class is best used to measure /// the relative performance of functions over many iterations. /// </summary> public sealed class StopwatchPlus : IDisposable {
        private long _startTick;
        private long _elapsed;
        private bool _isRunning;
        private string _name;
        private Action<StopwatchPlus> _startAction;
        private Action<StopwatchPlus> _stopAction;

        /// <summary> /// Creates an instance of the StopwatchPlus class and starts the timer. By /// providing a value to the name parameter, the Debug Console automatically /// will include the current values when the timer was started and stopped with /// this name. /// </summary> /// <param name="name"></param> public StopwatchPlus(string name)
            : this(name, WriteStart, WriteResults) { } 

        /// <summary> /// Creates an instance of the StopwatchPlus class and starts the timer. /// </summary> /// <param name="stopAction">Action to take when the Stop method is called.</param> public StopwatchPlus(Action<StopwatchPlus> stopAction)
            :this(null, stopAction)
        {
        }

        /// <summary> /// Creates an instance of the StopwatchPlus class and starts the timer. /// </summary> /// <param name="startAction">Action to take when the timer starts.</param> /// <param name="stopAction">Action to take when the Stop method is called.</param> public StopwatchPlus(Action<StopwatchPlus> startAction,
            Action<StopwatchPlus> stopAction)
            :this(null, startAction, stopAction)
        {
        }

        /// <summary> /// Creates an instance of the StopwatchPlus class and starts the timer. /// </summary> /// <param name="name">Name of this stop watch (used as output)</param> /// <param name="startAction">Action to take when the timer starts.</param> /// <param name="stopAction">Action to take when the Stop method is called.</param> public StopwatchPlus(string name,
            Action<StopwatchPlus> startAction, 
            Action<StopwatchPlus> stopAction)
        {
            _name = name;
            _startAction = startAction;
            _stopAction = stopAction;
            Start();
        }

        public string Name
        {
            get { return _name; }
        }

        /// <summary> /// Completely resets and deactivates the timer. /// </summary> public void Reset()
        {
            _elapsed = 0;
            _isRunning = false;
            _startTick = 0;
        }

        /// <summary> /// Begins the timer. /// </summary> public void Start()
        {
            if (!_isRunning)
            {
                _startTick = GetCurrentTicks();
                _isRunning = true;
                if (_startAction != null)
                {
                    _startAction(this);
                }
            }
        }

        /// <summary> /// Stops the current timer. /// </summary> public void Stop()
        {
            if (_isRunning)
            {
                _elapsed += GetCurrentTicks() - _startTick;
                _isRunning = false;
                if (_stopAction != null)
                {
                    _stopAction(this);
                }
            }
        }

        /// <summary> /// Gets a value indicating whether the instance is currently recording. /// </summary> public bool IsRunning
        {
            get { return _isRunning; }
        }

        /// <summary> /// Gets the Ellapsed time as a Timespan. /// </summary> public TimeSpan Ellapsed
        {
            get { return TimeSpan.FromMilliseconds(EllapsedMilliseconds); }
        }

        /// <summary> /// Gets the Ellapsed time as the total number of milliseconds. /// </summary> public long EllapsedMilliseconds
        {
            get { return GetCurrentElapsedTicks() / TimeSpan.TicksPerMillisecond; }
        }

        /// <summary> /// Gets the Ellapsed time as the total number of ticks (which is faked /// as Silverlight doesn't have a way to get at the actual "Ticks") /// </summary> public long EllapsedTicks
        {
            get { return GetCurrentElapsedTicks(); }
        }

        private long GetCurrentElapsedTicks()
        {
            return (long)(this._elapsed + (IsRunning ? (GetCurrentTicks() - _startTick) : 0));
        }

        private long GetCurrentTicks()
        {
            // TickCount: Gets the number of milliseconds elapsed since the system started. return Environment.TickCount * TimeSpan.TicksPerMillisecond;
        }

        #region IDisposable Members

        public void Dispose()
        {
            Stop();
        }

        #endregion private static void WriteStart(StopwatchPlus sw)
        {
            WriteStartInternal(sw);
        }

        // This is not called in a Release build [Conditional("DEBUG")]
        private static void WriteStartInternal(StopwatchPlus sw)
        {
            Debug.WriteLine("BEGIN\t{0}", sw._name);

        }

        private static void WriteResults(StopwatchPlus sw)
        {
            WriteResultsInternal(sw);
        }

        // This is not called in a Release build [Conditional("DEBUG")]
        private static void WriteResultsInternal(StopwatchPlus sw) 
        {
            Debug.WriteLine("END\t{0}\t{1}", sw._name, sw.EllapsedMilliseconds);
        }
    }
}

 

Silverlight vs. Flash vs. Photoshop Text Rendering

Curious about text rendering characteristics between Flash 10, Silverlight 4.0 and Photoshop, I created two small applications, one in Flash CS 4, and one in Visual Studio 2010. The Silverlight 4.0 tests are using the release candidate which was current as of March 23, 2010.

I used Times New Roman in all of the tests and used full text justification in all of the examples.

There are two types of text rendering available in Silverlight, Animated and ClearType (the default).

(You may click on images to see full size versions in a new window – I did that so that it would be easy for you to move the images around and do your own side-by-side comparisons).

image

Flash 10 has similar offerings, with an Anti-alias for readability and anti-alias for animation option:

 image

Side by side using the animated mode:

image

Then, the options for “readability” or the default compared side-by-side. (Note that I flipped the apps around so they’d layer correctly below)

image

To my eye, I preferred the Flash Anti-alias for Readability option just slightly over the other options. The animation friendly options left the text feeling too soft for me. What I didn’t like about the Silverlight ClearType option was the darkness of the font overall.

Zoomed in (with Silverlight on the top). You’ll see how the Silverlight text uses darker strokes overall.

 image

The levels of the “anti-alias for readability” text:

image

Whereas the levels of the Silverlight ClearType option:

image

Interesting difference.

I next tweaked the foreground color of the text in the Silverlight ClearType example, deciding that the black was too black for comfortable on screen reading.

I set the foreground of the text to #3d3d3d (click image to enlarge).

TweakSideBySide

Very close. So close that I’m not sure that I could distinguish between the two renderings without detailed study. The lightening of the foreground color made the text look a bit softer than the original, but not so far as to make the text blurry.

If you were wondering what the appearance of the text would be when aliased, here’s Photoshop’s rendering (I’m warning you … you wont’ like it!):

None

Below, you can see how the bowl of the letter ‘a’ in Flash 10 and the area along the meanline are nearly disconnected from the stem. This general lightness appears throughout Flash’s rendering of Times New Roman.

 image

Since my original inspiration was to determine how to make text look as sharp as possible when using Silverlight, I wanted to see what I liked best from what was available readily on my Windows 7 PC. I used:

  1. Chrome
  2. Firefox
  3. Flash (CS4 / 10.0)
  4. Internet Explorer 8
  5. Internet Explorer 9 Preview (March 2010)
  6. Adobe Illustrator CS4
  7. Adobe Photoshop CS5
  8. Safari
  9. Silverlight 4 (RC)

For a text-rendering side by side comparison, I put the block of text next to the text from Photoshop Crisp, Silverlight, and Flash.

Chrome

 Firefox

 FlashAnimation

 FlashReadability

 IE8

 IE9

 IllustratorCS4

PhotoshopNone

 PhotoshopCrisp

 PhotoshopSharp

 PhotoshopSmooth

 Safari

 SafariBestForLCD

 SilverlightAnimation

 SilverlightCleartype

 SilverlightCleartypeGray

 Word2007

Going further, I’ve created zoomed images of the lowercase letter “a” from the sources mentioned above. I’m amazed how differently an anti-aliased “a” can be when different companies all approach the problem with unique algorithms.

Chrome

Chrome

Firefox

Firefox

Flash Animation

 FlashAnimation

Flash Readability

FlashReadability

Internet Explorer 8

IE8

Internet Explorer 9

IE9

Adobe Illustrator CS 4

Illustrator

Photoshop (No Anti-alias option selected)

 PhotoshopNone

Photoshop Crisp

PhotoshopCrisp

Photoshop Sharp

 PhotoshopSharp

Photoshop Smooth

 PhotoshopSmooth

Safari (Default)

 Safari

Safari (Best for LCD)

 SafariBestForLCD

Silverlight Animation

SilverlightAnimation

Silverlight ClearType Default

SilverlightClearTypeDefault

Silverlight ClearType (Lightened)

silverlight-cleartype-gray

Microsoft Word 2007

Word

What’s your preference? Hope you found this useful.

(If you’re wondering how I created full-text-justification in Silverlight, I’ll provide the solution in a follow-on post).

My first Windows Phone 7 Experiment

OK, It doesn’t look like much. It’s not. I was experimenting with some CompositionTarget Rendering and WriteableBitmap stuff on the plane ride out to Mix 2010 on Sunday and created a little animating blue line thing. (I don’t know what to to call it). But it was a work in progress.

I took the code and copied it into a Windows Phone 7 Silverlight application and hit run. It worked right out of the box. (Using the CTP download from here).

image

CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering);

The event:

void CompositionTarget_Rendering(object sender, EventArgs e)
{
    int indx = _shapes.Count - 1;
    double littleCircleHeight = littleCircle.ActualHeight;

    while (indx >= 0)
    {
        Sparkle sparkle = _shapes[indx];
        sparkle.Y += sparkle.VY;
        sparkle.X += sparkle.VX;
        if (sparkle.Y - (littleCircleHeight / 2) > _rect.Height)
        {
            _shapes.RemoveAt(indx);
        }
        else
        {
            // grab the cached one
            TranslateTransform t = sparkle.Transform as TranslateTransform;
            t.X = sparkle.X - (littleCircle.ActualWidth / 2);
            t.Y = sparkle.Y - (littleCircleHeight / 2);
            _bitmap.Render(littleCircle, sparkle.Transform);
        }
        indx--;

    }

    int x = _random.Next(_bitmap.PixelWidth);
    int y = 0; 

    Sparkle newsparkle = new Sparkle();
    newsparkle.X = x;
    newsparkle.Y = y;
    newsparkle.VX = _random.NextDouble() + .001;
    newsparkle.VX = (_random.Next(10) > 5 ? newsparkle.VX : newsparkle.VX * -1.0);
    newsparkle.VY = _random.NextDouble() * 5 + 1.0;
    newsparkle.Transform = new TranslateTransform() { Y = newsparkle.Y, X = newsparkle.X };

    _shapes.Add(newsparkle);
    _bitmap.Invalidate();
}

Sizing:

void MainPage_SizeChanged(object sender, SizeChangedEventArgs e)
{
    _bitmap = new WriteableBitmap((int)e.NewSize.Width, (int)e.NewSize.Height);
    _bitmap.Clear(Colors.Black);
    _rect = new Rect(0, 0, e.NewSize.Width, e.NewSize.Height);
    dest.Source = _bitmap;
}

The sparkle class:

public class Sparkle
{
    public double X { get; set; }
    public double Y { get; set; }

    public double VX { get; set; }
    public double VY { get; set; }

    public Transform Transform { get; set; }
}

And it also uses a few functions from the WriteableBitmap extension classes on CodePlex.

It doesn’t perform nearly as well as it does in stand-alone Silverlight – but it’s running in a VM on my laptop, without accelerated graphics ….. I blame my hardware.

Custom RouteHandler in ASP.NET 4.0

It’s great that some of the innovations from ASP.NET MVC 1.0 were moved into the ASP.NET 4.0 platform. One of those was the RouteTable. I hadn’t written a custom RouteHandler before, so I thought I’d do a simple one as a demo for myself (and any others who are interested).

This is just an example of how to build one – it’s not secure.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.SessionState;
using System.Web.Routing;
using System.IO;

namespace TestWebApplication1
{
    public class Global : System.Web.HttpApplication
    {
        void Application_Start(object sender, EventArgs e)
        {
            RouteTable.Routes.Add(new Route("Assets/{locale}/{assetID}", new CustomRouteHandler()));
        }
    }

    public class CustomRouteHandler : IRouteHandler
    {
        public IHttpHandler GetHttpHandler(RequestContext requestContext)
        {            
            return new SimpleFileHttpHandler(requestContext);
        }
    }

    public class SimpleFileHttpHandler : IHttpHandler
    {
        private RequestContext _requestContext;
        private HttpContext _httpContext;

        public RequestContext RequestContext { get; private set; }

        public SimpleFileHttpHandler(RequestContext requestContext)
        {
            _requestContext = requestContext;
        }

        public bool IsReusable
        {
            get { return false; }
        }

        public void ProcessRequest(HttpContext context)
        {
            _httpContext = context;

            string assetID = _requestContext.RouteData.Values["assetID"].ToString();
            string locale = _requestContext.RouteData.Values["locale"].ToString();

            if (string.IsNullOrWhiteSpace(assetID) || string.IsNullOrWhiteSpace(locale)) {
                throw new ArgumentException();
            }
            // this is not adaquate and definitely not secure as it would allow any file to be selected and downloaded
            // needs to prevent any sort of hack attempts using encoded paths, etc. should just be a relative path within the
            // folder that contains the content and no more. 
            string path = _httpContext.Request.MapPath("~/Content/" + assetID, "", false);
            if (File.Exists(path))
            {               
                // hard coded to the image/jpg type (obviously needs to adjust)
                context.Response.AddHeader("Content-Type", "image/jpg");
                context.Response.AddHeader("Content-Length", new FileInfo(path).Length.ToString());
                context.Response.WriteFile(path);
            }
        }
    }


}

To use it, I created a folder called Content and copied one of the sample photos included with Windows into the folder, and then modified Default.aspx:

<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true"
    CodeBehind="Default.aspx.cs" Inherits="TestWebApplication1._Default" %>

<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent">
</asp:Content>
<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
    <h2>
        Welcome to the Route Table Demonstrator
    </h2>
    <h3>This one should work as it's using the not-so-magical route table and a custom iroutehandler.</h3>
    <p>
        &lt;img src=&quot;/Assets/en-us/Penguins.jpg&quot; width=&quot;320&quot; height=&quot;200&quot; /&gt;
    </p>
    <p>
        <img src="/Assets/en-us/Penguins.jpg" width="320" height="200" />
    </p>
    <h3>THis one shouldn't work due to security in web.config for the folder</h3>
    <p>&lt;img src=&quot;/Content/Penguins.jpg&quot; /&gt;</p>
    <p>
        <img src="/Content/Penguins.jpg" />
    </p>    
</asp:Content>

In the Content Folder, I added a web.config file to prevent direct access to the content within the folder:

<?xml version="1.0"?>
<configuration>

  <system.web>
    <authorization>
      <deny users="*"/>
    </authorization>
 
  </system.web>

  <system.webServer>
     <modules runAllManagedModulesForAllRequests="true"/>
  </system.webServer>
</configuration>

This should work without modification on IIS7+ and Visual Studio 2010.

image