 |
|
|
|
"Printing" in Silverlight 3 with WriteableBitmap
|
|
|
 |
|
Location: Blogs Andy's Blog |
|
| Posted by: host |
7/10/2009 8:30 AM |
One of the high-profile missing features in Silverlight has been Printing support. If you have ever tried to print a web page containing Silverlight content, what you saw on the printed page may be skewed or even missing altogether! So, what if you wanted to print a portion of your Silverlight screen, or take a “snapshot” image of the Silverlight UI to include in a report or other printable format?
Silverlight 3 can accomplish these scenarios using the WriteableBitmap API. WritableBitmap includes a Render method which can snag all of the pixels of a given UI Element and place them into a buffer for manipulation.
In this demo, I’ll show how to take a “snapshot” of a Silverlight UI screen, upload the image to a web server, and include it in a Report Viewer (RDLC) report. You could easily modify these steps to save the snapshot to an image file on the server, or otherwise manipulate the pixels.
VIEW THE DEMO
DOWNLOAD THE SOURCE
The steps used in the demo are as follows:
1. Render the Silverlight UI to a WriteableBitmap, passing in the UI Element and an arbitrary Transform. In the code below, the content of a Canvas named cnvSource is rendered to the WriteableBitmap. We pass in an empty TranslateTransform simply because one is required by the constructor:
WriteableBitmap bitmap = new WriteableBitmap(cnvSource, new TranslateTransform());
2. Convert the WriteableBitmap pixels to a PNG using Joe Stegman's PNG encoder.
EditableImage imageData = new EditableImage(bitmap.PixelWidth, bitmap.PixelHeight);
for (int y = 0; y < bitmap.PixelHeight; ++y)
{
for (int x = 0; x < bitmap.PixelWidth; ++x)
{
int pixel = bitmap.Pixels[bitmap.PixelWidth * y + x];
imageData.SetPixel(x, y,
(byte)((pixel >> 16) & 0xFF),
(byte)((pixel >> 8) & 0xFF),
(byte)(pixel & 0xFF),
(byte)((pixel >> 24) & 0xFF)
);
}
}
Stream pngStream = imageData.GetStream();
| NOTE that this PNG encoder does NOT include compression! This would be a good optimization to add, but also note that the GZipStream class is not present in Silverlight, so you would need to use an outside compression library such as SharpZipLib.
3. At this point, we have the PNG bytes in a stream, and you could take several approaches to get these bytes up to the server – such as using an Http Handler (ASHX). In this demo, we’ll place the bytes into a hidden field on the ASPX page and post the page back to the server for inclusion in a report. To do this, we’ll translate the PNG bytes into a string using Base64 encoding:
byte[] binaryData = new Byte[pngStream.Length];
long bytesRead = pngStream.Read(binaryData, 0, (int)pngStream.Length);
string base64String =
System.Convert.ToBase64String(binaryData,
0,
binaryData.Length);
// save the encoded PNG bytes to the page
HtmlDocument document = HtmlPage.Document;
HtmlElement txtPNGBytes = document.GetElementById("txtPNGBytes");
txtPNGBytes.SetProperty("value", base64String);
// this calls a js function "postBackPrint" which will cause a postback
HtmlPage.Window.CreateInstance("postBackPrint", new string[] { }); |
4. Now that we have our bytes up on the server, we can decode them and feed them to a ReportViewer (RDLC) report. This will give us a nicely printed format and the ability to export to PDF:
string bytes64 = Request["txtPNGBytes"];
byte[] imageBytes = System.Convert.FromBase64String(bytes64);
DSReportPrintImage ds = new DSReportPrintImage();
DataRow drImage = ds.Tables[0].NewRow();
drImage["ImageBytes"] = imageBytes;
ds.Tables[0].Rows.Add(drImage);
ReportViewer1.LocalReport.ReportPath = "ReportPrintSilverlight.rdlc";
ReportDataSource src = new ReportDataSource("DSReportPrintImage_ImageData", ds.Tables[0]);
ReportViewer1.LocalReport.DataSources.Add(src);
ReportViewer1.LocalReport.Refresh(); |
That’s it! I really think this use of WriteableBitmap as a snapshot/print function will be useful in some of my projects that need to capture the current view of the Silverlight application.
|
|
| Permalink |
Trackback |
Comments (25)
Add Comment
|
Re: "Printing" in Silverlight 3 with WriteableBitmap
|
By Anonymous on
7/10/2009 2:18 PM
|
I want to save my image to hard drive using SaveFileDialog. Can you help me ?
|
|
|
andy.beaulieu.com > Home - "Printing" in Silverlight 3 with WriteableBitmap
|
By TrackBack on
7/11/2009 7:59 AM
|
Thank you for submitting this cool story - Trackback from NewsPeeps # NewsPeeps
|
|
|
Re: "Printing" in Silverlight 3 with WriteableBitmap
|
By Anonymous on
7/11/2009 9:46 AM
|
|
Can you help me i want to save the image to a sql database with a webservice but i don't now how to do that.
|
|
|
Re: "Printing" in Silverlight 3 with WriteableBitmap
|
By Anonymous on
7/11/2009 11:08 AM
|
|
If you want to save the image using SaveFileDialog please visit http://silverlight.net/forums/p/108713/247204.aspx
|
|
|
Re: "Printing" in Silverlight 3 with WriteableBitmap
|
By Anonymous on
7/12/2009 1:26 PM
|
Thank you. I tried the demo, it work but seemed to be very slow for me. maybe just the network?
|
|
|
Re: "Printing" in Silverlight 3 with WriteableBitmap
|
By Anonymous on
7/12/2009 2:23 PM
|
Excellent post !!!
I have combined your code with some bit and pieces of another one and created a lib to export a canvas to PNG:
http://geekswithblogs.net/braulio/archive/2009/07/12/export-canvas-to-png-and-save-it-in-your-local.aspx
Thanks a lot Braulio
|
|
|
Re: "Printing" in Silverlight 3 with WriteableBitmap
|
By Anonymous on
7/12/2009 7:40 PM
|
Yeah, sorry the demo is slow. Note that the image data is NOT compressed, and in a real world scenario you should add compression using a library like SharpZipLib (see notes in post). In the demo it is sending about 700kb up to the server and it is slow but the compression would take care of that.
-Andy
|
|
|
|
Re: "Printing" in Silverlight 3 with WriteableBitmap
|
By Anonymous on
7/18/2009 5:25 AM
|
Add following method to PngEncoder class and you'll be able to save writable bitmap
public static Stream Encode(WriteableBitmap wb) { int w = wb.PixelWidth; int h = wb.PixelHeight; int adr;
int rowLength = w * 4 + 1;
byte[] buffer = new byte[rowLength * h];
for (int y = 0; y < h; y++) { buffer[y * rowLength] = 0; // filter byte
for (int x = 0; x < w; x++) { adr = y * rowLength + x * 4 + 3;
int pixel = wb.Pixels[x + y * w];
buffer[adr--] = (byte)(pixel & 0xff); pixel >>= 8; buffer[adr--] = (byte)(pixel & 0xff); pixel >>= 8; buffer[adr--] = (byte)(pixel & 0xff); pixel >>= 8; buffer[adr] = (byte)(pixel & 0xff); } }
return PngEncoder.Encode(buffer, w, h); }
|
|
|
Re: "Printing" in Silverlight 3 with WriteableBitmap
|
By Anonymous on
7/27/2009 9:07 PM
|
|
My canvas that I want to print contains a scrollviewer. Is it possible to print items that are not visible and must be scrolled to?
|
|
|
Re: "Printing" in Silverlight 3 with WriteableBitmap
|
By Anonymous on
7/27/2009 9:44 PM
|
For the ScrollViewer, I suppose you would have to place some kind of a Container inside the ScrollViewer, and then use the method above to save or print that Container to the WriteableBitmap.
-Andy
|
|
|
Re: "Printing" in Silverlight 3 with WriteableBitmap
|
By Anonymous on
7/27/2009 10:01 PM
|
Hi Andy, Thanks for replying so quickly. The problem is I have a header-detail scenario with the canvas. The header is a grid and the detail is a scrollviewer containing a grid. So if I printed the scrollviewer contents it would have no header.
|
|
|
Re: "Printing" in Silverlight 3 with WriteableBitmap
|
By Anonymous on
7/28/2009 12:07 PM
|
Hmmm... yeah that is a good one. I can't think of a clean way. But as a hack, you could maybe repeat the header in another Canvas, and then Remove (Children.Remove()) the Grid from the ScrollViewer and add it to the new Canvas before the print. Then put it back in the ScrollViewer after the Print.
Of course you still would need to figure out Paging if you need it :( That is a big flaw in this "screenshot" method.
-Andy
|
|
|
Re: "Printing" in Silverlight 3 with WriteableBitmap
|
By Anonymous on
7/29/2009 8:55 AM
|
|
When trying the demo, I note that the PNG is more whiter than the original
|
|
|
Re: "Printing" in Silverlight 3 with WriteableBitmap
|
By Anonymous on
7/29/2009 9:52 AM
|
|
I think the brightness change is due to the way the RDLC viewer is displaying it? Because if you export to PDF the color looks normal.
|
|
|
Re: "Printing" in Silverlight 3 with WriteableBitmap
|
By Anonymous on
8/3/2009 11:20 PM
|
Hi Andy,
I'm not familiar with the SharpZip libriary. How and where in your code would you use it? Please let me know. Thanks.
|
|
|
Re: "Printing" in Silverlight 3 with WriteableBitmap
|
By Anonymous on
8/6/2009 1:12 PM
|
Worth noting here is that the "screendump" cannot contain images that comes from a cross-domain site. If they do you won't' be able to read the pixels, and this approach breaks. Yet another reason for proper printing support in Silverlight.
Also note that Joe's PngEncoder is slooooow. Nikola optimized this code A LOT: http://blogs.msdn.com/nikola/archive/2009/03/04/silverlight-super-fast-dymanic-image-generation-code-revisited.aspx
|
|
|
Re: "Printing" in Silverlight 3 with WriteableBitmap
|
By Anonymous on
10/5/2009 6:06 AM
|
|
could you please publish the vb project also.
|
|
|
Re: "Printing" in Silverlight 3 with WriteableBitmap
|
By Anonymous on
10/19/2009 10:41 PM
|
|
This project might be helpful for printing: http://silverpdf.codeplex.com
|
|
|
Re: "Printing" in Silverlight 3 with WriteableBitmap
|
By Anonymous on
11/12/2009 1:06 PM
|
Hi,
I'm creating a very simple drawing program and I would like to be able to save the drawing to my web server (via JSP). I am using some of your code:
Stream pngStream = imageData.GetStream(); byte[] binaryData = new Byte[pngStream.Length]; long bytesRead = pngStream.Read(binaryData, 0, (int)pngStream.Length); string base64String = System.Convert.ToBase64String(binaryData, 0, binaryData.Length);
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(new Uri("http://myserver.com/scripts/GetImage.jsp?" + base64String));
but, I get an exception on the HttpWebRequest call.
System.UriFormatException: [net_uri_SizeLimt]
Any ideas?
Frank
|
|
|
Re: "Printing" in Silverlight 3 with WriteableBitmap
|
By Anonymous on
11/12/2009 5:55 PM
|
Hi Frank,
You wouldn't be able to fit an image that large in a Querystring variable. Querystrings are limited to 2 kb in size if I remember right. So you'll need to put it in a form variable like the demo shows.
Also note that the image data is NOT being compressed in the sample. You should take a look at the Image Tools on Codeplex - http://imagetools.codeplex.com/ - to get a better compression out of it.
-Andy
|
|
|
Re: "Printing" in Silverlight 3 with WriteableBitmap
|
By Anonymous on
11/14/2009 1:42 AM
|
Hi,
I have a problem with the DOM access: HtmlElement txtPNGBytes = document.GetElementById("txtPNGBytes");
The txtPNGBytes html element always null but is exists in the aspx page: ...
...
Do you know anybody what is the problem?
|
|
|
Re: "Printing" in Silverlight 3 with WriteableBitmap
|
By Anonymous on
1/20/2010 2:29 AM
|
I am currently working on Silverlight application and have issues creating report. Any help on creating a simple report with code (may be to print 2 columns from one table) would be highly appreciated. Thanks for your help. Prasad
|
|
|
Re: "Printing" in Silverlight 3 with WriteableBitmap
|
By Anonymous on
2/17/2010 12:51 AM
|
Hello Guys
I am having problem with Height of the control that i have to print public int Height { get { return _height; } set { if (_init) { OnImageError("Error: Cannot change Height after the EditableImage has been initialized"); } else if ((value <= 0) || (value > 2047)) { OnImageError("Error: Height must be between 0 and 2047"); } else { _height = value; } } }
If value is greater then "value > 2047" Then it creates a problem while create string date from Byte array.
Is any one facing same issue.
Please let me know what is the best solution for same.
Right now i m going to try for split the array
|
|
|
Re: "Printing" in Silverlight 3 with WriteableBitmap
|
By Anonymous on
2/17/2010 12:52 AM
|
Hello Guys
I am having problem with Height of the control that i have to print public int Height { get { return _height; } set { if (_init) { OnImageError("Error: Cannot change Height after the EditableImage has been initialized"); } else if ((value <= 0) || (value > 2047)) { OnImageError("Error: Height must be between 0 and 2047"); } else { _height = value; } } }
If value is greater then "value > 2047" Then it creates a problem while create string date from Byte array.
Is any one facing same issue.
Please let me know what is the best solution for same.
Right now i m going to try for split the array
|
|
|
|
 |
|
 |
|
|