XAML Playground
about XAML and other Amenities

Take a screenshot with Silverlight 5.0 and pInvoke

2012-02-28T00:36:14+01:00 by codeblock

PInvoke is a new entry in Silverlight 5.0 runtime. For the one that are not aware of what is pInvoke I will say it stands for "Platform Invoke". It is a set of classess and attributes that allows the Silverlight runtime to access the low-level Win32 API of Windows Operating System.

After Silverlight 4.0 brought COM interop in Silverlight, the team decided to accomplish the last step and integrate also pInvoke. As you understand, it is only restricted to Windows OS, but is some scenario it may be a very useful thing to cross the subtle line from "can't do" to "can do". Wether you have to detect USB keys, interact with devices and so on, pInvoke require you to directly access the API preparing the mapping to the OS funzions and handling unmanaged resources.

As a littel sample I ported an old example to Silverlight. The code below implements a simple funzion that is able to take a screenshot of the desktop.

   1: public static class ScreenCapture
   2: {       
   3:   public static WriteableBitmap GetDesktopImage()
   4:   {
   5:       WriteableBitmap bmap = null;
   6:  
   7:       // initialize unmanager pointers
   8:       IntPtr hDC = IntPtr.Zero,
   9:              hMemDC = IntPtr.Zero,
  10:              desktop = IntPtr.Zero;
  11:  
  12:       try
  13:       {
  14:           // get a reference to desktop
  15:           desktop = User32.GetDesktopWindow();
  16:           // get an handle to Device Context
  17:           hDC = User32.GetDC(desktop);
  18:           // create a new Device Context in memory
  19:           hMemDC = Gdi32.CreateCompatibleDC(hDC);
  20:  
  21:           // read size of desktop window
  22:           Size size = new Size
  23:           {
  24:               Width = User32.GetSystemMetrics(Gdi32.SM_CXSCREEN),
  25:               Height = User32.GetSystemMetrics(Gdi32.SM_CYSCREEN)
  26:           };
  27:  
  28:           // create a bitmap compatible with desktop
  29:           IntPtr hBitmap = Gdi32.CreateCompatibleBitmap(hDC, size.Width, size.Height);
  30:  
  31:           if (hBitmap != IntPtr.Zero)
  32:           {
  33:               // select memory Device Context into the new bitmap
  34:               IntPtr hOld = (IntPtr)Gdi32.SelectObject(hMemDC, hBitmap);
  35:  
  36:               // copy the desktop to the memory handle
  37:               Gdi32.BitBlt(hMemDC, 0, 0, size.Width, size.Height, hDC, 0, 0, (int)TernaryRasterOperations.SRCCOPY);
  38:  
  39:               // select the memory Device Context into the bitmap
  40:               Gdi32.SelectObject(hMemDC, hOld);
  41:  
  42:               // initialize a bitmap info
  43:               BitmapInfo bi = new BitmapInfo 
  44:               { 
  45:                   biSize = Marshal.SizeOf(typeof (BitmapInfo)), 
  46:                   biWidth = size.Width, 
  47:                   biHeight = size.Height, 
  48:                   biPlanes = 1, 
  49:                   biBitCount = 32, 
  50:                   biCompression = 0, 
  51:                   biSizeImage = 0, 
  52:                   biXPelsPerMeter = 0, 
  53:                   biYPelsPerMeter = 0, 
  54:                   biClrUsed = 0, 
  55:                   biClrImportant = 0 
  56:               };
  57:  
  58:               // calculate byte size of the area
  59:               int dwBmpSize = ((size.Width * bi.biBitCount + 31) / 32) * 4 * size.Height;
  60:               byte[] data = new byte[dwBmpSize];
  61:  
  62:               // initialize a WriteableBitmap to output the result
  63:               bmap = new WriteableBitmap(size.Width, size.Height);
  64:  
  65:               // copy bitmap to byte array
  66:               Gdi32.GetDIBits(hMemDC, hBitmap, 0, (uint)size.Height, data, ref bi, 0);
  67:  
  68:               // compy byte array to WriteableBitmap
  69:               for (int i = 0; i < data.Length; i += 4)
  70:               {
  71:                   int y = size.Height - ((i / 4) / size.Width) - 1;
  72:                   int x = (i / 4) % size.Width;
  73:                   int pixel = data[i + 0] | (data[i + 1] << 8) | (data[i + 2] << 16) | (data[i + 3] << 24);
  74:                   bmap.Pixels[y * size.Width + x] = pixel;
  75:               }
  76:           }
  77:       }
  78:       finally
  79:       {
  80:           // release unmanaged resources
  81:           if (hMemDC != IntPtr.Zero)
  82:               Gdi32.DeleteDC(hMemDC);
  83:           if (hDC != IntPtr.Zero)
  84:               User32.ReleaseDC(desktop, hDC);
  85:       }
  86:  
  87:       // return bitmap
  88:       return bmap;
  89:   }
  90: }

I wrote a number of comments inside the code example to try explain how it works, but in a few words it create a buffer in memory, take a reference to the desktop and copies it to the new area. Then finally it copy again the buffer to a byte array to allow managed code to access the captured image. It is copied to a WriteableBitmap and then returned to the caller.

To use the class you can simply call the static method and check for the result value and exceptions. The code automatically handles the unmanaged resources and is done to freed them before to exit.

   1: private void Button_Click(object sender, RoutedEventArgs e)
   2: {
   3:     Application.Current.MainWindow.Hide();
   4:  
   5:     Delay(1000,
   6:           () =>
   7:           {
   8:               try
   9:               {
  10:                   BitmapSource source = ScreenCapture.GetDesktopImage();
  11:  
  12:                   if (source != null)
  13:                       screenshot.Source = source;
  14:                   else
  15:                       MessageBox.Show("Impossibile completare la cattura!");
  16:               }
  17:               catch (Exception ex)
  18:               {
  19:                   MessageBox.Show(ex.Message);
  20:               }
  21:               finally
  22:               {
  23:                   Application.Current.MainWindow.Show();
  24:                   Application.Current.MainWindow.Activate();
  25:               }
  26:           });
  27: }
  28:  
  29: private void Delay(int timeout, Action action)
  30: {
  31:     Task task = new Task(
  32:         () =>
  33:         {
  34:             Thread.Sleep(timeout);
  35:  
  36:             Deployment.Current.Dispatcher.BeginInvoke(action);
  37:         });
  38:  
  39:     task.Start();
  40: }

This code minimizes the main window and then take a screenshot. Finally it reactivate the window and take it to the front. To compile this code you have to import a number of structures. The attached sample includes all the needed classes.

Download: XPG.PInvoke.zip

Categories:   Imaging | News
Actions:   E-mail | del.icio.us | Permalink | Comments (1) | Comment RSSRSS comment feed

Silverlight 3.0: WriteableBitmap

2009-03-18T18:44:00+01:00 by Andrea Boschin

The use of vector graphics is really powerful because it connect the low weight for big images and free scalability to every size. Nevertheless sometimes you need to return to raster images because they grant a more contro to the rendering pixel by pixel and for photographic rendering are better. With Silverlight 2.0 the only way to use raster images is to download the content from the server, and embed it into an element <image> on the UserControl.

With Silverlight 3.0 a new way to manage raster images has been added. Using a WriteableBitmap it is possible to generate images on the fly. This tecnique is really powerful and someway unite the best of both vector graphic and raster images. Using this class you may render big images withoud downloading them from the source server.

Using WriteableBitmap: Back to Mandelbrot

mandelbrot_smallTo give an example of the great power of image generation let me explain how to create a simple silverlight application to generate Mandelbrot fractals on the fly. The core of the application is a short algorithm, that render a Mandelbrot set into a bitmap.

A is one of many set of fractals, probably the most famous, explored for the first by . The Fractal geometry is out of the scope of this article so let me skip a deep explanation of the fractals theory. If you need you may find many informations .

The only thing we need to know here is that a Mandelbrot Fractals may be zoomed and panned like a map and this generate a large set of wonderful image. You may see an example in the side image that show also the interface of the application I'm going to explain.

The first problem we have to address is the time needed to generate the image. This may be a time consuming activity but also may consume a lot of resources. In a silverlight application this may become to a freeze of the browser during the rendering. To avoid this problem we have to use a background thread that calculate every single pixel and write it to the raster image. Another little problem is that the WriteableImage is a UI element and it is not possible to write inside of it in a separated thread. So we have to calculate all the pixels in a System.Int32 array and at the end of the elaboration we switch to the UI thread and copy the array to the WritableBitmap. Here is the code to render the image:

   1: // Declare a buffer to generate the image
   2: int[] bmap = new int[this.Width * this.Height];
   3:  
   4: // Calculate here the Mandelbrot image into the int [].
   5: // ...
   6:  
   7: // Mashal to the UI Thread before render
   8: this.UIElement.Dispatcher.BeginInvoke(
   9:     new Action<int[]>(
  10:         data =>
  11:         {
  12:             // create the bitmap
  13:             WriteableBitmap output = new WriteableBitmap(this.Width, this.Height);
  14:  
  15:             try
  16:             {
  17:                 
  18:                 
  19:                 for (int i = 0; i < data.Length; i++) 
  20:                     output.Pixels[i] = data[i];
  21:  
  22:                 // do something with generated image
  23:             }
  24:             finally
  25:             {
  26:                 output.Invalidate();
  27:                 
  28:             }
  29:  
  30:         }), bmap);

From this short snippet we may learn a bunch or things: first of all writing a raster image has to be made pixel-by-pixel like we have to fill and array. The data is stored from the top line to the last line and from the left to the right. So at the position zero we have the top left pixel, then we continue with the first line and after the end we begin the second line and so on. This is a simple conversion from X and Y using Width of the image as modulus:

int pixelIndex = Y * Width + X;

The resulting image may be used directly in a ImageSource Property to show the generated image in the interface.

The sample application

The application is sligthly simple. We have an initial rectangle from -2.1 to 1.0 horizontally and from -1.3 to 1.3 vertically. These are the default "coordinates"  that show the full Mandelbrot set as you have probably already saw. We have a method Redraw() that accepts the UI coordinated of the part to render. The initial value is the full size of the canvas but wen we select a part of the image or click one of the panning and zooming buttons the square is recalculated and the part of the set become the full image. In thes way we can zoom to the most little particular of the set and see the fractas in all his beauty.

The generation of the image is done by a special class that incapsulate the algorithm and the thread marshaling. The only thing we get by this class called MandelbrotGenerator is the Completed event containing the generated image.

Here is a full size screenshot of the application:

Full size screenshot of fractal explorer

Some other words about WriteableBitmap

One thing I've missed speaking about this class in that the WritableBitmap can render a part of the VisualTree into an image. This may be useful to create effects like mirroring where all or part of an UIElement is rendered rotated on a bitmap and showed on the bottom of the original element to simulate a reflection effect. Unfortunately the Render() method does not work correctly in this beta. It is affected by the position of the elements in the Visual Tree so you have to surround the elements to render by a Border and sometimes this do not work (or at least I cannot get it working). So I will post a new article when this object will be fully working in the future releases.

Download Code: (867 KB)

Demo Video: (3.5 MB)

Tags:   ,
Categories:   Imaging
Actions:   E-mail | del.icio.us | Permalink | Comments (4) | Comment RSSRSS comment feed