Converting Silverlight InkPresenter images to a PNG file

In the TabletPC SDKs and in WPF it’s very easy to take an ink image and save it to an image format – BMP, JPG, etc.

Then came Silverlight and the InkPresenter and naturally I wanted to do the same. But it wasn’t so easy.

Silverlight itself isn’t bogged down with that functionality. So you first need to get the XAML representation of the Ink and send it to a service where either the TabletPC SDK or the WPF APIs are available. Even then you are not home free because the Silverlight ink is not quite the same as either of the other two. So you then need to extract data from the XAML representation of the Silverlight ink and create a new object for whichever API you choose.

This was all done in Silverlight 1.0. I haven’t pulled this into Silverlight 1.1/2.0 yet, but it should all be the same and you still have to do the conversion on the server side. THe only difference of course, is the javascript needs to be converted to .NET code on the client side.

Even then, there is still some more trickery because there is something strange with using the width from the Silverlight object and I spent hours just experimenting with getting the proportions to display properly in the image. I also spent a lot of time struggling with the colors because the javascript output of the color values doesn’t line up with what WPF wants. You’ll see in the code comments all of the conversions going on.

Once I had all of that worked out (and this represents hours of effort) there were still some issues. Luckily, Stefan Wick, who is the ultimate guru on this topic and finally has a blog – hoorah!, was able to set me straight (and trim some  of my code down significantly).

I hadn’t thought much of this nor, apparently had anyone else, until someone recently emailed asking me how I did it so that he can use it as part of a solution in a competition. (I hope that the requirements of the competition don’t say anything about original work!), so I thought I would blog the steps.

1) Convert the InkPresenter data to XAML . This does two things. FIrst it enables you to serialize it and pass it to a web service and secondly, it is the lowest reasonable common denominator for sharing between different objects. This javascript code comes from Gavin Gear.

This javascript code reads through the StrokeCollection property of an InkPresenter and builds up a string of xml that is the XAML representation of the StrokeCollection. You could also take the resulting string and pass it to CreateFromXAML to recreate the Silverlight StrokeCollection object.

  if (strokeCollection.Count>0)  
{
    var xaml = "<StrokeCollection>";
   if (strokeCollection != null)
   {
for (var i = 0; i < strokeCollection.Count; i++)
{
  var stroke = strokeCollection.GetItem(i);
  if (stroke.Name>"")
    xaml += "<Stroke Name='" + stroke.Name + "'><Stroke.DrawingAttributes>";
  else
    xaml += "<Stroke><Stroke.DrawingAttributes>";
    xaml += "<DrawingAttributes ";
    xaml += "Color='" + BrowserColorConverter
            (stroke.DrawingAttributes.Color) + "' ";
    xaml += "OutlineColor='" + convertColorToHexString
               (stroke.DrawingAttributes.OutlineColor) + "' ";
    xaml += "Width='" + stroke.DrawingAttributes.Width + "' ";
    xaml += "Height='" + stroke.DrawingAttributes.Height + "' ";
    xaml += "/></Stroke.DrawingAttributes>";
    xaml += "<Stroke.StylusPoints>";
    for (var j = 0; j < stroke.StylusPoints.Count; j++)
    {
      var stylusPoint = stroke.StylusPoints.GetItem(j);
      xaml += "<StylusPoint X='" + 
               roundToTwoDecimalPlaces(stylusPoint.X) + "' Y='" +   
roundToTwoDecimalPlaces(stylusPoint.Y) + "' />"; } xaml += "</Stroke.StylusPoints></Stroke>"; } } xaml += "</StrokeCollection>";

2) Pass this string to a web service method that will do the following to it

3) Create a WPF InkObject from the XAML. Now that I’m in the web service, I can use .NET code. Phew. Note that I did this before I knew how to use LINQ to XML so I struggled through XPath to get this. Watch for an upcoming MSDN Mag article that will have updated code.

private static StrokeCollection InkObjectfromXAML(XmlNode StrokeColl)
{
  StrokeCollection objStrokes = new StrokeCollection();
  XmlNodeList strokeElements =StrokeColl.SelectNodes("Stroke");
  foreach (XmlNode strokeNodeElement in strokeElements)
  {
   //step 1: create a new stroke from the stylus point elements in the XAML
    XmlNodeList stylusPointElements = strokeNodeElement
               .SelectNodes("./Stroke.StylusPoints/StylusPoint");
    XmlNode drawAttribs = strokeNodeElement 
               .SelectSingleNode("./Stroke.DrawingAttributes");
    //points node is sent to GetStrokePOints method to convert to a type 
//that can be used by the new stroke
System.Windows.Input.StylusPointCollection strokeData = GetStrokePoints(stylusPointElements); Stroke newstroke = new Stroke(strokeData); //step 2: grab color metadata about stroke from the xaml //color is a hex value //the stroke object requires a System.Windows.Media.Color type //following code performs the conversion string mycolor = drawAttribs.FirstChild.Attributes["Color"].Value; Drawing.Color drwColor = Drawing.ColorTranslator.FromHtml(mycolor); //build the new color from the a,r,g,b values of the drawing.color Windows.Media.Color newColor = new Windows.Media.Color(); newColor.A = drwColor.A; newColor.R = drwColor.R; newColor.G = drwColor.G; newColor.B = drwColor.B; //Step 3: extract width data from xaml, convert to int int myIntWidth; bool parseSuccess = int.TryParse (drawAttribs.FirstChild.Attributes["Width"].Value,
out myIntWidth); //Step 4: apply width & color to stroke //some really wierd unexplainable transformations that I had to get     // around until the final images looked right. if (myIntWidth == 3) newstroke.DrawingAttributes.Width = 1.5; else newstroke.DrawingAttributes.Width = 2; newstroke.DrawingAttributes.Color = newColor; //Step 5: add stroke to the stroke collection objStrokes.Add(newstroke); } return objStrokes; } //The GetStrokePoints method (called from the method above, //is an abstraciton of some sample code from Microsoft.
private static Windows.Input.StylusPointCollection GetStrokePoints(XmlNodeList stylusPointElements) {
Windows.Input.StylusPointCollection pointData = new Windows.Input.StylusPointCollection(); //The object requires HiMetric point values, // create multiplier for conversion double pixelToHimetricMultiplier = (2540d / 96d) / 100; foreach (XmlNode stylusPointElement in stylusPointElements) { string xStr = stylusPointElement.Attributes["X"].Value; string yStr = stylusPointElement.Attributes["Y"].Value; //x and y are in pixels, //we need to multiply them to get them into HIMETRIC //space, which is what the InkAnalyzerBase expects int xInHimetric = (int)(Convert.ToDouble(xStr) * pixelToHimetricMultiplier); int yInHimetric = (int)(Convert.ToDouble(yStr) * pixelToHimetricMultiplier); pointData.Add(new Windows.Input.StylusPoint(xInHimetric, yInHimetric)); } return pointData; }

 Now we have an inkObject that WPF will be happy with!

4) Convert WPF Ink to PNG format bytes This is with a BIG thanks to Stefan – we need to start a separate thread to do the conversion from WPF Ink ojbect to PNG. That conversion happens inside the thread. Also, thank to his deep understanding of the ink object, Stefan was able to accomplish in a much smaller amount of code what I had achieved in about 3 times as much code. I was definitely doing loop-dee-loops, but it was the best I could come up at the time.

Note that I am not saving to an actual file here, just creating the bytes because my goal was to store that in a database.

private static void ThreadforConverttoPNG()
{
  Thread t = new Thread(new ThreadStart(ConverttoPNG));
  t.SetApartmentState(ApartmentState.STA);
// Start ThreadProc.Note that on a uniprocessor, the new 
// thread does not get any processor time until the main thread 
// is preempted or yields.Uncomment the Thread.Sleep that 
// follows t.Start() to see the difference.
t.Start();
   }

private static void ConverttoPNG()
{
  //I had originally achieved this with a LOT more code.
  // This is Stefan's more trimmed down method
  //create temporary InkCanvas
  InkCanvas inkCanvas = new InkCanvas();
  inkCanvas.Strokes = strokes;
  //render InkCanvas to a RenderBitmapTarget
  Rect rect = inkCanvas.Strokes.GetBounds();
  RenderTargetBitmap rtb = new RenderTargetBitmap((int)rect.Right,
          (int)rect.Bottom, 96d, 96d, Windows.Media.PixelFormats.Default);
  rtb.Render(inkCanvas);
  //endcode as PNG
  BitmapEncoder pngEncoder = new PngBitmapEncoder();
  pngEncoder.Frames.Add(BitmapFrame.Create(rtb)); 
  //save to memory stream
  System.IO.MemoryStream ms = new System.IO.MemoryStream(); 
  pngEncoder.Save(ms);ms.Close();strokeBytes= ms.ToArray();
} 

5) My next step was actually to store the bytes into a database. I wasn’t actually saving out to a file. But to do that is simple. System.IO.File lets you create a new file on the fly from a byte array.

   System.IO.File.WriteAllBytes("C:\\myfile.png",strokeBytes); 

So, those are all of the pieces from converting a silverlight inkpresenter image to an image file.

  Sign up for my newsletter so you don't miss my conference & Pluralsight course announcements!  

15 thoughts on “Converting Silverlight InkPresenter images to a PNG file

  1. @Dunisha,Sure and if it’s a good consulting gig for you, buy me a cup of coffee sometime. ;-)Seriously, I’m glad that the code is useful for you.julie

  2. I get error when execute this source code! I run with VS 2008 and IIS 5.1.. Error like this "An error occurred while trying to make a request to URI ‘http://localhost:1274/InkToPng.svc’. This could be due to attempting to access a service in a cross-domain way without a proper cross-domain policy in place, or a policy that is unsuitable for SOAP services. You may need to contact the owner of the service to publish a cross-domain policy file and to ensure it allows SOAP-related HTTP headers to be sent. This error may also be caused by using internal types in the web service proxy without using the InternalsVisibleToAttribute attribute. Please see the inner exception for more details." in page Reference.cs and procedur EndSave.

    Why i get error?

  3. hi,emmie here i am doing <a href="http://www.mcseguide.com">mcse certification</a> and i wana know about it,that how to convert InkPresenter images to png, Shawn Wildermuth with an iGoogle Gadget in SL, and Jesse Liberty comes to our aid with a partial explanation of the 4001 error you might be seeing.

  4. Hi I am new to Silverlight 3, I am Facing a Problem.. I Used Inkpresenter then I converted the image in form of "stream" using FluX Jpeg Core & now I want to save this Stream as a Image into Server Side… Please Help…

  5. sorry shan no clue what flux jpeg core is. This info is for SL2 and we’re up to SL4 so I can’t confirm that it’s still valid. Try MSDN forums maybe??

  6. Here is the method you can add into your web service to save the image in byte[] to a file on server side.

    public void UploadFile(byte[] f, string fileName)

    {

    MemoryStream ms = new MemoryStream(f);

    FileStream fs = new FileStream

    (System.Web.Hosting.HostingEnvironment.MapPath("~/ClientBin/ExportImg/") +

    fileName, FileMode.Create);

    ms.WriteTo(fs);

    ms.Close();

    fs.Close();

    fs.Dispose();

    }

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.