Archive for the ‘Imaging’ Category

How to print dynamically created Images in Silverlight 4 Beta

Wednesday, November 25th, 2009

Silverlight 4 supports printing scenarios. It’s quite easy. Just create a PrintDocument instance, handle the PrintPage-Event and call the Print-Method. In the PrintPage-Event set the PageVisual-Property of the PrintPageEventArgs to a UIElement of your choice. If there are more pages, set the HasMorePages-Property of the PrintPageEventArgs to true and the PrintPage-Eventhandler would be called again for the next page.

Below a simple example using a lambda expression. When the Print-Method is called a PrintDialog is displayed to the User, where he can select the printer of his choice. When the PrintDialog was accepted, the PrintPage-Event gets fired and the lambda expression below get’s called. The PageVisual-Property is set to a TextBlock. So that TextBlock with the text “Thoams says…” is printed out.

var pd = new PrintDocument();
pd.PrintPage += (s, e) =>
  {
    e.PageVisual = new TextBlock {Text="Thomas says Hello"};
  };
pd.Print();

Ok, so far so good. As I was working on an example for my upcoming Silverlight 4 book I needed to create an Image-Element on the fly and print this out. And then I noticed that the Image doesn’t appear on the output.

While searching for a solution I found somebody having the same problem in this thread in Microsoft’s Silverlight forums:

http://forums.silverlight.net/forums/t/145680.aspx

So, it seemed it was not my cause, it was a Beta-cause. So let’s look at a workaround. But first look at the bug.

I made a smaller example to reproduce it. View the following code. What do you think is printed on the page?

void PrintButton_Click(object sender, RoutedEventArgs e)
{
  var streamResourceInfo =
    Application.GetResourceStream(
      new Uri("thomas.png", UriKind.Relative));

  var bitmapImage = new BitmapImage();
  bitmapImage.SetSource(streamResourceInfo.Stream);

  var image = new Image
  {
    Width = bitmapImage.PixelWidth,
    Height = bitmapImage.PixelHeight,
    Source = bitmapImage
  };

  var pd = new PrintDocument();
  pd.PrintPage += (s, args) =>
    {
      args.PageVisual = image;
    };
  pd.Print();
}

Right, an Image should be printed on the page. But it isn’t. The page is empty. Well, the next thing I tried was to call Measure, Arrange and UpdateLayout on the Image to force a layout-pass. But anyway, it didn’t work, the printed page is always empty.

When the Image isn’t created on the fly, it works. Define the Image in XAML like this

<Image Source="thomas.png" x:Name="image"/>

and a Print-Method in the Codebehind-File would work like that:

void PrintButton_Click(object sender, RoutedEventArgs e)
{
  var pd = new PrintDocument();
  pd.PrintPage += (s, args) =>
    {
      args.PageVisual = image;
    };
  pd.Print();
}

But we want to print an Image on the fly. So how to do that? One way I found out was to create an ImageBrush and set its ImageSource-Property to the BitmapImage. Use the ImageBrush for a Rectangle’s Fill-Property and print out that Rectangle. So here is some code to dynamically print an image by using an ImageBrush in combination with a Rectangle:

void PrintButton_Click(object sender, RoutedEventArgs e)
{
  var streamResourceInfo =
    Application.GetResourceStream(
      new Uri("thomas.png", UriKind.Relative));

  var bitmapImage = new BitmapImage();
  bitmapImage.SetSource(streamResourceInfo.Stream);

  var imageBrush = new ImageBrush();
  imageBrush.ImageSource = bitmapImage;

  var rectangle = new Rectangle
  {
    Width = bitmapImage.PixelWidth,
    Height = bitmapImage.PixelHeight,
    Fill = imageBrush
  };

  var pd = new PrintDocument();
  pd.PrintPage += (s, args) =>
    {
      args.PageVisual = rectangle;
    };
  pd.Print();
}

And voilà, the output looks like this when printed to my PDFCreator-Printer:

image

[Download the Source]

Cheers Thomas

Take Snapshots PART II – Save as animated GIF

Wednesday, April 9th, 2008

I was asked, if it would be possible to save the snapshots created in my last post as an animated gif. With a DispatcherTimer and the GifBitmapEncoder-class you’re not far away from it. Just create a MediaElement in your Window:

<MediaElement Source="thomasOnBoard.wmv" 
  x:Name="media" MediaOpened="media_MediaOpened"
  MediaEnded="media_MediaEnded" Width="300"
  Height="200" Stretch="Fill"/>

In the Codebehind-File, implement Eventhandlers for MediaOpened and MediaEnded-Events. In MediaOpened you start the DispatcherTimer. I’ve just used an interval of 100 milliseconds. In the Tick-Eventhandler, which is called each 100 milliseconds, a BitmapFrame is added to the GifBitmapEncoders Frames-Property. When the video ends, the MediaEnded-Eventhandler writes the GIF to your disk and opens the file. That’s it. :-)

public partial class Window1 : Window
{
... GifBitmapEncoder _encoder; DispatcherTimer _timer; private void media_MediaOpened(object sender, ...) { _timer = new DispatcherTimer(); _timer.Interval = TimeSpan.FromMilliseconds(100); _timer.Tick += OnTick; _timer.Start(); } void OnTick(object sender, EventArgs e) { Size dpi = new Size(96, 96); RenderTargetBitmap bmp = new RenderTargetBitmap(300, 200, dpi.Width, dpi.Height, PixelFormats.Pbgra32); bmp.Render(media); if (_encoder == null) _encoder = new GifBitmapEncoder(); _encoder.Frames.Add(BitmapFrame.Create(bmp)); } private void media_MediaEnded(object sender, ...) { _timer.Tick -= OnTick; _timer = null; string filename = Guid.NewGuid().ToString() + ".gif"; FileStream fs = new FileStream(filename, FileMode.Create); _encoder.Save(fs); fs.Close(); _encoder = null; Process.Start(filename); } }

Here’s the gif saved from my video. Just click on it to see it animated:

withWPFCreated

Take Snapshots of Videos with WPF

Sunday, April 6th, 2008

With WPF’s Imaging-Classes you can take snapshots of any Visual. The snapshot can be saved in any common Image-Format, like e.g. JPG. Let’s take a look at a pretty short example, that shows how easy this can be done. The example takes snapshots of a Video.

The following Window contains a MediaElement and a Button. The MediaElement plays the Video thomasOnBoard.wmv. The Button defines an Eventhandler for the Click-Event. It takes a snapshot of the video, when you click it.

<Window x:Class="SnapShots.Window1"
    xmlns=http://schemas.microsoft.com/winfx/2006/xaml/...
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300"
ResizeMode="NoResize"> <StackPanel> <MediaElement x:Name="media" Height="200" Stretch="Fill"> <MediaElement.Triggers> <EventTrigger RoutedEvent="MediaElement.Loaded"> <BeginStoryboard> <Storyboard> <MediaTimeline
Source="thomasOnBoard.wmv" RepeatBehavior="Forever"/> </Storyboard> </BeginStoryboard> </EventTrigger> </MediaElement.Triggers> </MediaElement> <Button Click="Button_Click" Content="Snapshot"/> </StackPanel> </Window>

Let’s look at the Eventhandler of the Button. An instance of the RenderTargetBitmap-class is created with some parameters about image-size, dots per inch (dpi) and Pixelformat. The Render-Method gets the MediaElement as a parameter, so MediaElements visual appearance is stored in the RenderTargetBitmap in memory. With a JpegBitmapEncoder and a FileStream the Image is written as a JPG to disk. That’s it.

void Button_Click(object sender, RoutedEventArgs e)
{
  Size dpi = new Size(96,96);
  RenderTargetBitmap bmp = 
    new RenderTargetBitmap(300, 200, 
      dpi.Width, dpi.Height, PixelFormats.Pbgra32);
  bmp.Render(media);

  JpegBitmapEncoder encoder = new JpegBitmapEncoder();
  encoder.Frames.Add(BitmapFrame.Create(bmp));

  string filename = Guid.NewGuid().ToString()+".jpg";
  FileStream fs = new FileStream(filename,FileMode.Create);
  encoder.Save(fs);
  fs.Close();

  Process.Start(filename);
}

Instead of taking the picture in the Button_Click eventhandler, you could create a Timer and take an Image every 0.1s. That allows you to extract an image-sequence of your videos. As it works for any Visual, and everything that’s on the screen in a WPF-Application is a visual, there are many things you can do with it. You could create a snapshot of an Image drawn to an inkCanvas, upload it to a webserver to display it on a webpage etc.