Thursday, January 28, 2010

Rounder, Faster, Better - WriteableBitmapEx 0.9.0.0

A lot of things happened since the release of the last WriteableBitmapEx version. I used the time I have to wait for a third party [spoiler] until I could release my Silverlight Augmented Reality framework SLARToolkit [/spoiler] to work on the WriteableBitmapEx project. Beside some optimizations and smaller extensions, the main addition in the new version is the support for parametric curves. I implemented the cubic Beziér spline and the Cardinal spline. The Cardinal spline is not part of Silverlight's and WPF's Geometry nor shape classes, but the DrawCurve method is part of GDI+ and many people appreciate the characteristics of cardinal splines. Fortunately the WriteableBitmapEx library fills this gap now for the WriteableBitmap.

New features
  • Draw methods for parametric curves: Cubic Beziér splines and the Cardinal splines similar to the GDI+ API. 
  • Clone method to copy the whole WriteableBitmap to a new instance.
  • Boundary check for the Draw* (Shape) methods. The coordinates are now clipped to the WriteableBitmap's size.
  • A new, even faster DrawLine method overload that uses the bitmap's pixels int array, width and height directly as parameters. This method should be used if many lines are drawn to the same WriteableBitmap.
  • Optimization of the byte array conversion methods using the Buffer.BlockCopy method. This improvement was suggested in this comment.
  • WriteTga method to write a WriteableBitmap as a TGA image to a stream. The original method was provided in this blog post.
  • Fast path for Blitting using the Buffer.BlockCopy method if the BlendMode is None and the bitmap should not be tinted. The result is a performance boost by factor 3.5 compared to traditional loop iteration. This improvement was suggested in this CodePlex discussion
  • Adam Kinney contributed two new blend modes for the Blit method: Multiply and Mask. He used these in his cool torn photo effect.
  • A new method that takes a function as parameter and iterates over each pixel and sets its color. The method is called ForEach and has two overloads. Usage example:
    writeableBmp.ForEach((x, y, color) => Color.FromArgb(color.A, color.R / 2, (byte)(x * y), 100));
    The method was suggested in this CodePlex discussion
The code listing on the project's CodePlex site was updated to show the usage of the new methods.
    Live
    I also wrote a new sample application that shows the spline methods in action. The sample starts with a demo animation that uses the Cardinal spline DrawCurve method to draw an artificial plant that grows procedurally. The other part of the sample is interactive and allows to draw and manipulate Beziér and Cardinal splines with the mouse.



    Uncheck the "Growing plant demo" Checkbox to change to the interactive mode where you can add new or move curve control points with the left mouse button. Press the DEL key while a control point is selected to remove it. The used curve type can be changed with the Radio Buttons. A cardinal spline needs at least 3 points and a cubic Beziér 4 points, a start point, the control point 1, the control point 2 and the end point.The Slider controls the tension of the Cardinal spline. The tension defines the shape of the curve and is usually between 0 and 1. A value of 0 would be a straight line.
    Uncheck the "Points" Checkbox to hide the control points and press the Clear Button to remove all control points. Click the Save Button to write the WriteableBitmap to a TGA image file.
    The other controls should be self-explaining.

    Get it while it's hot
    The WriteableBitmapEx library is hosted at CodePlex. You can find the new binary release here.

      Monday, January 25, 2010

      Do you block Google Analytics tracking in your browser?

      Recently I checked the server logs for my website and compared the statistics with the Google Analytics data for the timespan. I expected that the page views and visits won't be the same, but I haven't expected such a large difference I encountered.
      Just to make it clear, I don't use Google Analytics for evil, I'm not Google and I'm not interested in advertising or web marketing, I'm just curious what my visitors (you) are looking for and what caught the interest, so I can provide more content in this field.
      I know some people that block Analytics tracking in their browser because of the Google octopus, or disabled Java Script at all.

      The difference between the Google Analytics statistics and the server logs raised the question in me how reliable the Google Analytics data is when several people block it in their browser. I started to search for some block statistics, but couldn't find any useful information and I also think the actual blocking-rate does crucially depend on the website's audience and used technology. That's why I created a little poll at twtpoll.com and asked my Twitter friends to vote. Big thanks to all that already voted, but not all of you are on Twitter, so I like to ask here too:

      Do you block Google Analytics tracking in your browser?

      I will publish the results here on my blog after the poll is over on Feb 28, 2010. Thanks for taking your time.

      Update 03-10-2010
      The results are now available.

      Monday, January 18, 2010

      Matrix3DEx 1.0 - When PlaneProjection is not enough

      A new year has begun and I started a new open source project at CodePlex. It's called Matrix3DEx and is an extension library for the Silverlight Matrix3D struct. Most of the functionality I implemented in Matrix3DEx was originally required for another open source project I'm currently working on (hint, hint), and as you might know I also like to extend Silverlight's graphics functionality in a reusable manner. That's why I decided to extract the Matrix3D code into a separate open source project, add some more useful methods, document it and write a sample.


      The Matrix3DEx project description from the CodePlex site:
      The Matrix3DEx library is a collection of extension and factory methods for Silverlight's Matrix3D struct. The Matrix3D struct represents a 4x4 matrix that is used in combination with the Matrix3DProjection to apply more complex semi-3D scenarios to any UIElement than are possible with the simple PlaneProjection. This makes it possible to apply arbitrary model transformation matrices and perspective matrices to Silverlight elements.
      The Matrix3D struct is very minimalistic and has only a few members. The Matrix3DEx library tries to compensate that with extension and factory methods for common transformation matrices that are easy to use like built in methods.

      Features:
      • Factory methods

        • Translation, scaling and rotation around x, y, z or any defined axis
        • Perspective field of view and orthographic projection
        • Camera (look-at) with position, target and up vector
        • Support for left-handed and right-handed coordination systems

      • Extension methods

        • Calculation of the matrix' determinant
        • Matrix transpose
        • SwapHandedness to change from right-handed to left-handed coordination system and vice versa
        • Dump of the values row by row into a formatted string

      • Math helper methods

        • Angle conversion from degrees to radians and vice versa


      Some use cases where Silverlight's semi-3D projection is needed can be implemented with the PlaneProjection, but there are also some scenarios where the PlaneProjection wouldn't work or only with a lot of effort. One example is the usage of a physics library that returns a matrix for an object. Traditional matrix transformations are an elegant alternative for such cases and the Matrix3DEx library has factory and extension methods for all the common matrices.

      Live
      Open the sample in a new page.



      The application loads a bunch of photos asynchronously and randomizes the position vector of each. The sample uses most of the Matrix3DEx features and has some Sliders and CheckBoxes to change the parameters. Uncheck the "Animate" CheckBox to disable the camera movement and click on an Image to select it. The translation, scaling and the rotation matrices of the selected element can be changed with the corresponding Sliders. You can move the camera, change the target and the "Field Of View" with the other Sliders or fix the camera target at the selected element.
      The basic functionality of the sample might also be done with the PlaneProjection, but a separate look-at matrix (camera) simplifies the code a lot and makes it easier to read. Other things like a custom field of view are not possible with the PlaneProjection.

      How it works
      The sample uses a DipatcherTimer to call the Update method every 100 milliseconds.
      The core of the Update method:
      // Create global transformations
      var vw = Viewport.Width;
      var vh = Viewport.Height;
      var invertYAxis = Matrix3DFactory.CreateScale(1, -1, 1);
      var translate   = Matrix3DFactory.CreateTranslation(TranslateX, 
                                                          TranslateY,
                                                          TranslateZ);         
      var rotateX     = Matrix3DFactory.CreateRotationX(MathHelper.ToRadians(RotateX));
      var rotateY     = Matrix3DFactory.CreateRotationY(MathHelper.ToRadians(RotateY));
      var rotateZ     = Matrix3DFactory.CreateRotationZ(MathHelper.ToRadians(RotateZ));
      var scale       = Matrix3DFactory.CreateScale(ScaleX, ScaleY, ScaleZ);
      var lookAt      = Matrix3DFactory.CreateLookAtLH(CameraX, CameraY, CameraZ,
                                                       CameraLookAtX, 
                                                       CameraLookAtY, 
                                                       CameraLookAtZ);
      var viewport    = Matrix3DFactory.CreateViewportTransformation(vw, vh);
      var fieldOfView = MathHelper.ToRadians(FieldOfView);
      var projection  = Matrix3DFactory.CreatePerspectiveFieldOfViewLH(fieldOfView, 
                                                                       vw / vh,
                                                                       NearPlane,
                                                                       FarPlane);
      
      // Transform all elements
      var selectedMatrix = Matrix3D.Identity;
      foreach (var elem in this.Elements)
      {
         // The UIElement
         var e = elem.Element;
      
         // Create transformation matrices for UIElement
         var centerAtOrigin = Matrix3DFactory.CreateTranslation(-e.ActualWidth * 0.5,
                                                                -e.ActualHeight * 0.5, 
                                                                0);
         var baseTranslate = Matrix3DFactory.CreateTranslation(elem.PositionX, 
                                                               elem.PositionY,
                                                               elem.PositionZ);
      
         // Combine the transformation matrices
         var m = Matrix3D.Identity;
         m = m * centerAtOrigin;
         m = m * invertYAxis;
      
         // Apply addtional world transformations to the seleced element
         if (elem == SelectedElement)
         {
            m = m * scale;
            m = m * rotateX * rotateY * rotateZ;
            m = m * translate;
      
            // Should the camera target be fixed at the selected element?
            if (ChkLookAtSelected.IsChecked.Value)
            {
               lookAt = Matrix3DFactory.CreateLookAtLH(CameraX, CameraY, CameraZ, 
                                                       elem.PositionX, 
                                                       elem.PositionY, 
                                                       elem.PositionZ);
            }
         }
      
         // Calculate the final view projection matrix
         m = m * baseTranslate;
         m = Matrix3DFactory.CreateViewportProjection(m, lookAt, projection, viewport);
      
         // Apply the matrix to the UIElement
         e.Projection = new Matrix3DProjection { ProjectionMatrix = m };
      }
      

      First the global transformation matrices like camera projection are created using the left-handed Matrix3DEx factory methods. After that the element local transformations are calculated and the final matrix is applied to the UIElement's Projection property.
      See the project site for another simplified code listing.

      Go and grab it

      The open source Matrix3DEx library is hosted at CodePlex and released under the Microsoft Public License (Ms-PL) license. If you have any comments, questions or suggestions don't hesitate and write a comment, use the Issue Tracker on the CodePlex site or contact me via any other media.
      Have fun with the library and let me know if it was useful for you.

      Sunday, January 10, 2010

      Goodbye GPL

      In the past I've released the source code that mostly comes along with my blog posts under the GPLv3 license to ensure that changes on the code always stay open and never get closed. But the GPL has a major drawback, it's too viral, which means if someone uses GPL licensed code the new code has to be released under the GPL too. And if someone uses this new code, the new code 2 has to be put under the GPL again and so on. And as the FSF states this even applies when the code is just linked dynamically with a GPL'ed library only except if it uses the GPL linking exception. That's why I wanted to move away from the GPL and searched for some alternatives. Finally I decided to use the Ms-PL license from now on if my work doesn't depend on GPL infected code. I've chosen the Ms-PL because it's approved by the OSI, it's short and easy understandable, the weak copyleft guarantees that changes on the original code have to be committed back and that it stays open and it could be used for commercial products. Jon Galloway (@jongalloway) summarized it nicely in his Twitter reply to me: "Ms-PL is essentially BSD + patent protection." So the Ms-PL is suitable, short, simple and keeps the trolls away.

      I have changed all the source code headers and License.txt files and updated the Zip files with the source code you find linked on most blog posts. If you still find a GPL reference, just ignore it and if the Ms-PL is a problem for you, just contact me and I might put the relevant code under a second license.