Save wpf view as image, preferably .png - c#

I have searched and understand how to save an image in WPF by using BmpBitmapEncoder. My program has a MVVM view that I want to save as an image. Is it possible to set it as BitmapFrame so I can encode it? If so, is there an online tutorial?
Listed below is the view I want to save.
<Grid>
<view:OverallView Grid.Row="1"
Visibility="{Binding IsOverallVisible,Converter={StaticResource B2VConv}}" />
</Grid>
OverallView is a user control.
If setting a view as a BitmapFrame is not possible, what wpf elements can be set as a BitmapSource/Frame?

You can return it as RenderTargetBitmap:
public static RenderTargetBitmap GetImage(OverallView view)
{
Size size = new Size(view.ActualWidth, view.ActualHeight);
if (size.IsEmpty)
return null;
RenderTargetBitmap result = new RenderTargetBitmap((int)size.Width, (int)size.Height, 96, 96, PixelFormats.Pbgra32);
DrawingVisual drawingvisual = new DrawingVisual();
using (DrawingContext context = drawingvisual.RenderOpen())
{
context.DrawRectangle(new VisualBrush(view), null, new Rect(new Point(), size));
context.Close();
}
result.Render(drawingvisual);
return result;
}
After that you can use the PngBitmapEncoder to save it as PNG and save it to stream, e.g.:
public static void SaveAsPng(RenderTargetBitmap src, Stream outputStream)
{
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(src));
encoder.Save(outputStream);
}
FIX: bitmap => result

Kudos to the accepted answer above for leading me down the path to a solution, but that's actually over-complicating the requirements. Specifically, you don't need to create a DrawingVisual to pass to the Render call. You can pass the view directly since it's already a Visual, which the Render call accepts.
Here's a simplified version that I've also changed into an extension method on FrameworkElement so you can use it easily with any control.
Note: Although Render accepts a Visual, you can't extend Visual since you need the actual width and height to create the RenderTargetBitmap, but chances are for that same reason you're probably already using a FrameworkElement anyway.
public static RenderTargetBitmap? CopyAsBitmap(this FrameworkElement frameworkElement) {
var targetWidth = (int)frameworkElement.ActualWidth;
var targetHeight = (int)frameworkElement.ActualHeight;
// Exit if there's no 'area' to render
if (targetWidth == 0 || targetHeight == 0)
return null;
// Prepare the rendering target
var result = new RenderTargetBitmap(targetWidth, targetHeight, 96, 96, PixelFormats.Pbgra32);
// Render the framework element into the target
result.Render(frameworkElement);
return result;
}
I then have a second extension method that takes any BitmapSource (which RenderTargetBitmap is a subclass of) and returns the bytes based on the provided encoder.
public static byte[] Encode(this BitmapSource bitmapSource, BitmapEncoder bitmapEncoder){
// Create a 'frame' for the BitmapSource, then add it to the encoder
var bitmapFrame = BitmapFrame.Create(bitmapSource);
bitmapEncoder.Frames.Add(bitmapFrame);
// Prepare a memory stream to receive the encoded data, then 'save' into it
var memoryStream = new MemoryStream();
bitmapEncoder.Save(memoryStream);
// Return the results of the stream as a byte array
return memoryStream.ToArray();
}
And here's how you use it all together. This code renders any FrameworkElement as a PNG:
var renderTargetBitmap = someFrameworkElement.CopyAsBitmap();
var pngData = renderTargetBitmap.Encode(new PngBitmapEncoder());
File.WriteAllBytes(#"C:\Users\SomeUser\Desktop\TestOutput.png", pngData);
Or more succinctly...
var pngData = someFrameworkElement.CopyAsBitmap().Encode(new PngBitmapEncoder());
File.WriteAllBytes(#"C:\Users\SomeUser\Desktop\TestOutput.png", pngData);
To instead render it as a JPG, just change the encoder, like so...
var jpegData = someFrameworkElement.CopyAsBitmap().Encode(new JpegBitmapEncoder());
File.WriteAllBytes(#"C:\Users\SomeUser\Desktop\TestOutput.jpg", jpegData);
Additionally, since RenderTargetBitmap is ultimately an ImageSource (via BitmapSource), you can also set it as the Source property of an Image control directly, like so...
someImage.Source = someFrameworkElement.CopyAsBitmap();

Related

wpf - how to print a whole page to pdf with PDFSharp

So I want to know how to print my entire WPF page to a PDF file with PDFSharp.
I've already been looking at several articles but I can't seem to figure it out.
I want the pdf to look something like this:
I've already looked up on articles about drawing strings, lines name it. But creating every line, string and shape individually looks like a sloppy and bad idea to me.
Can anyone help me with this?
Articles will also be appreciated!
Thanks in advance
I Would say first export your control to image with RenderTargetBitmap and then use a library to export it to PDF.
Maybe this sample might help ?
http://www.techcognition.com/post/Create-PDF-File-From-WPF-Window-using-iTextsharp-1001
With this library
Here his the Control to Image class I'm using with sucess (I'm able to get a PNG snapshot of complex UI controls implying a very deep parent-child hierarchy)
The source is a WPF control container (usercontrol, grid, wahtever).
The path is the full path for PNG output file (C:\Temp\myImage.png)
public class ControlToImageSnapshot
{
/// <summary>
/// Conversion du controle en image PNG
/// </summary>
/// <param name="source">Contrôle à exporter</param>
/// <param name="path">Destination de l'export</param>
/// <param name="zoom">Taille désirée</param>
public static void SnapShotPng(FrameworkElement source, string path, double zoom = 1.0)
{
try
{
var dir = Path.GetDirectoryName(path);
if (dir != null && !Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
}
RenderTargetBitmap renderTarget = new RenderTargetBitmap((int)source.ActualWidth, (int)source.ActualHeight, 96, 96, PixelFormats.Pbgra32);
VisualBrush sourceBrush = new VisualBrush(source);
DrawingVisual drawingVisual = new DrawingVisual();
DrawingContext drawingContext = drawingVisual.RenderOpen();
using (drawingContext)
{
drawingContext.DrawRectangle(sourceBrush, null, new Rect(new Point(0, 0), new Point(source.ActualWidth, source.ActualHeight)));
}
renderTarget.Render(drawingVisual);
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(renderTarget));
using (FileStream stream = new FileStream(path, FileMode.Create, FileAccess.Write))
{
encoder.Save(stream);
}
createPdfFromImage(path, #"C:\Temp\myfile.pdf");
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
public static void createPdfFromImage(string imageFile, string pdfFile)
{
using (var ms = new MemoryStream())
{
var document = new iTextSharp.text.Document(iTextSharp.text.PageSize.LETTER.Rotate(), 0, 0, 0, 0);
PdfWriter.GetInstance(document, new FileStream(pdfFile, FileMode.Create));
iTextSharp.text.pdf.PdfWriter.GetInstance(document, ms).SetFullCompression();
document.Open();
FileStream fs = new FileStream(imageFile, FileMode.Open);
var image = iTextSharp.text.Image.GetInstance(fs);
image.ScaleToFit(document.PageSize.Width, document.PageSize.Height);
document.Add(image);
document.Close();
//open pdf file
Process.Start("explorer.exe", pdfFile);
}
}
}
For pdfsharp its quite easy, you can pass in an array of bytes for the pdf image and pdf, ive used this function quite a lot when dealing with images in pdfsharp.
fairly self explanatory
Open pdf and the image into a memorystream
Get pdf setup and choose page to draw on
I always set interpolate to false, I get better results with the kind of images I'm dealing with, if you have shading in your image set it to true.
then all your left to do is draw the image on the pdf and return as a memorystream
public static byte[] AddImageToPdf(byte[] pdf, byte[] img, double x, double y)
{
using (var msPdf = new MemoryStream(pdf))
{
using (var msImg = new MemoryStream(img))
{
var image = Image.FromStream(msImg);
var document = PdfReader.Open(msPdf);
var page = document.Pages[0];
var gfx = XGraphics.FromPdfPage(page);
var ximg = XImage.FromGdiPlusImage(image);
ximg.Interpolate = false;
gfx.DrawImage(
ximg,
XUnit.FromCentimeter(x),
XUnit.FromCentimeter(y),
ximg.PixelWidth * 72 / ximg.HorizontalResolution,
ximg.PixelHeight * 72 / ximg.HorizontalResolution);
using (var msFinal = new MemoryStream())
{
document.Save(msFinal);
return msFinal.ToArray();
}
}
}
}
its hardcoded for page 1 in the pdf, easily extendable to pass in pages if you want, ill leave that as an exercise for yourself, at the end you get a nice byte array containing your pdf, no files need to touch the ground enroute if you import your image as a memorystream from your control and pass it in. another answer in this topic has a good way of getting the control image.

Image size is drastically increasing after applying a simple watermark

I've a set of images that I'm programmatically drawing a simple watermark on them using System.Windows and System.Windows.Media.Imaging (yes, not with GDI+) by following a tutorial in here.
Most of the images are not more than 500Kb, but after applying a simple watermark, which is a text with a transparent background, the image size is drastically increasing.
For example, a 440Kb image is becoming 8.33MB after applying the watermark with the below method, and that is shocking me.
private static BitmapFrame ApplyWatermark(BitmapFrame image, string waterMarkText) {
const int x = 5;
var y = image.Height - 20;
var targetVisual = new DrawingVisual();
var targetContext = targetVisual.RenderOpen();
var brush = (SolidColorBrush)(new BrushConverter().ConvertFrom("#FFFFFF"));
brush.Opacity = 0.5;
targetContext.DrawImage(image, new Rect(0, 0, image.Width, image.Height));
targetContext.DrawRectangle(brush, new Pen(), new Rect(0, y, image.Width, 20));
targetContext.DrawText(new FormattedText(waterMarkText, CultureInfo.CurrentCulture, FlowDirection.LeftToRight,
new Typeface("Batang"), 13, Brushes.Black), new Point(x, y));
targetContext.Close();
var target = new RenderTargetBitmap((int)image.Width, (int)image.Height, 96, 96, PixelFormats.Default);
target.Render(targetVisual);
var targetFrame = BitmapFrame.Create(target);
return targetFrame;
}
I've noticed that the image quality is improved compared than the original image. The image is more smoother and colors are more lighter. But, you know I don't really want this. I want the image to be as it is, but include the watermark. No quality increases, and of course no drastic changes in image size.
Is there any settings that I'm missing in here to tell my program to keep the quality as same as source image? How can I prevent the significant change of the image size after the changes in my ApplyWatermark method?
Edit
1. This is how I convert BitmapFrame to Stream. Then I use that Stream to save the image to AmazonS3
private Stream EncodeBitmap(BitmapFrame image) {
BitmapEncoder enc = new BmpBitmapEncoder();
enc.Frames.Add(BitmapFrame.Create(image));
var memoryStream = new MemoryStream();
enc.Save(memoryStream);
return memoryStream;
}
2. This is how I get the BitmapFrame from Stream
private static BitmapFrame ReadBitmapFrame(Stream stream) {
var photoDecoder = BitmapDecoder.Create(
stream,
BitmapCreateOptions.PreservePixelFormat,
BitmapCacheOption.None);
return photoDecoder.Frames[0];
}
3. This is how I read the file from local directory
public Stream FindFileInLocalImageDir() {
try {
var path = #"D:\Some\Path\Image.png";
return !File.Exists(path) ? null : File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read);
} catch (Exception) {
return null;
}
}
The problem is that when you edit the image, the compression is gone. A 730x1108 JPG with 433kB disc size with 32bit (you mentioned transparency, so ARGB) will need at least 730 * 1108 * 4 = 3,09MB on disc. Of course you can compress it afterwards again (for disc, network stream of what else).
This is the reason why image software always needs much memory even when working with compressed data.
Conclusion: You will need the free memory to work with the image. Not possible to have it otherwise completly at hand.
The reason I asked my question in the comments earlier, is because I noticed there were several different encoders available. A bitmap usually has a significantly larger file size, due to the amount of information it's storing about your image.
I haven't tested this myself, but have you tried a different encoder?
var pngEncoder = new PngBitmapEncoder();
pngEncoder.Frames.Add(ApplyWatermark(null, null));
MemoryStream stm = File.Create(image);
pngEncoder.Save(stm);
return stm;

Snapshot of canvas with small filesize - C# WPF

I have a puzzle game and I can create levels. When I save a level, it takes a snapshot of the canvas and then when I choose a level, it displays all the pictures of the levels next to their name as a thumbnail. However each image is around 1MB in size. I would like to get them to around 30KB in size. Also the file it makes cannot be edited by a photo editor to make it a smaller size even though it is a jpg. I see I have used a TiffBitmapEncoder whoops. Probably my issue with the photo editors.
Here is my code:
private void saveImage(object sender, EventArgs e)
{
string path = myImageNamePath;
FileStream fs = new FileStream(path, FileMode.Create);
RenderTargetBitmap bmp = new RenderTargetBitmap((int)myLevelDesigner.pbxMap.ActualWidth,
(int)myLevelDesigner.pbxMap.ActualHeight, 1 / 96, 1 / 96, PixelFormats.Pbgra32);
bmp.Render(myLevelDesigner.pbxMap);
BitmapEncoder encoder = new TiffBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bmp));
encoder.Save(fs);
fs.Close();
}
Oh my, is there a particular reason you are using the TIFF file format? I highly recommend using PNG instead, it is lossless and compressed.
Use PngBitmapEncoder instead, most graphics programs support PNG.
I would also recommend making it into an extension method that you can reuse throughout. Something like this:
public static class CanvasExtender
{
public static void SaveToImageFile(this Canvas canvas, string outputFile)
{
canvas.UpdateLayout();
var bitmap = new RenderTargetBitmap(canvas.ActualWidth, canvas.ActualHeight, 96d, 96d, PixelFormats.Pbgra32);
bitmap.Render(canvas);
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmap));
using(var outputStream = File.Create(outputFile))
encoder.Save(outputStream);
}
}

Image/ImageSource/Interop Image to bytearray

I'm developing a control where user can set an image and i want this this to be as user friendly as possible - so support for copy & paste, drag & drop.
I've got this part working using IDataObjects, testing for fileformats of FileDrop, FileContents (eg from outlook), and bitmap eg:
private void GetImageFromIDataObject(IDataObject myIDO)
{
string[] dataformats = myIDO.GetFormats();
Boolean GotImage = false;
foreach (string df in dataformats)
{
if (df == DataFormats.FileDrop)
{
// code here
}
if (df == DataFormats.Bitmap)
{
// Source of my problem here... this gets & displays image but
// how do I then convert from here ?
ImageSource myIS = Utilities.MyImaging.ImageFromClipboardDib();
ImgPerson.Source = myIS;
}
}
}
The ImageFromClipboard code is Thomas Levesque's as referenced in the answer to this SO question wpf InteropBitmap to bitmap
http://www.thomaslevesque.com/2009/02/05/wpf-paste-an-image-from-the-clipboard/
No matter how I get the image onto ImgPerson, this part is working fine; image displays nicely.
When user presses save I need to convert the image to a bytearray and send to a WCF server which will save to server - as in, reconstruct the bytearray into an image and save it in a folder.
For all formats of drag & drop, copy & paste the image is some form of System.Windows.Media.Imaging.BitmapImage.
Except for those involving the clipboard which using Thomas's code becomes System.Windows.Media.Imaging.BitmapFrameDecode.
If I avoid Thomas's code and use:
BitmapSource myBS = Clipboard.GetImage();
ImgPerson.Source = myBS;
I get a System.Windows.Interop.InteropBitmap.
I can't figure out how to work with these; to get them into a bytearray so I can pass to WCF for reconstruction and saving to folder.
Try this piece of code
public byte[] ImageToBytes(BitmapImage imgSource)
{
MemoryStream objMS = new MemoryStream();
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(imgSource));
encoder.Save(objMS);
return objMS.GetBuffer();
}
You can also use JpegBitmapEncoder, BmpBitmapEncoder based on your requirements.
byte[] arr = ImageToBytes(ImgPerson.Source as BitmapImage);
I can't believe I didn't see this SO question but the this is essentially the same as my question:
WPF: System.Windows.Interop.InteropBitmap to System.Drawing.Bitmap
The answer being:
BitmapSource bmpSource = msg.ThumbnailSource as BitmapSource;
MemoryStream ms = new MemoryStream();
BitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bmpSource));
encoder.Save(ms);
ms.Seek(0, SeekOrigin.Begin);
System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(ms);
So similar in execution to Nitesh's answer but crucially, works with an inter-op bitmap.

How to convert every page in a XPS file to an image in C#?

Is there a way to convert every page in a XPS document to an image programmatically using C#?
I ran across this blog post from Josh Twist that appears to do what you want.
Cracking an XPS in WPF
On searching the net, there are many paid/trial programs that claim to do this (I have not tried any of them, so I can't vouch/list any of them). I assumed you want to write your own code.
Here is the 'meat' of the blog post (condensed):
Uri uri = new Uri(string.Format("memorystream://{0}", "file.xps"));
FixedDocumentSequence seq;
using (Package pack = Package.Open("file.xps", ...))
using (StorePackage(uri, pack)) // see method below
using (XpsDocument xps = new XpsDocument(pack, Normal, uri.ToString()))
{
seq = xps.GetFixedDocumentSequence();
}
DocumentPaginator paginator = seq.DocumentPaginator;
Visual visual = paginator.GetPage(0).Visual; // first page - loop for all
FrameworkElement fe = (FrameworkElement)visual;
RenderTargetBitmap bmp = new RenderTargetBitmap((int)fe.ActualWidth,
(int)fe.ActualHeight, 96d, 96d, PixelFormats.Default);
bmp.Render(fe);
PngBitmapEncoder png = new PngBitmapEncoder();
png.Frames.Add(BitmapFrame.Create(bmp));
using (Stream stream = File.Create("file.png"))
{
png.Save(stream);
}
public static IDisposable StorePackage(Uri uri, Package package)
{
PackageStore.AddPackage(uri, package);
return new Disposer(() => PackageStore.RemovePackage(uri));
}
Please refer to the accepted answer it is really helpful (it helped me also) .I just want to note to some very important point in that solution.
if you are implementing your own DocumentPaginator (as in my case) then that code will Not work because we will not get reference to your specific Paginator from the statement
DocumentPaginator paginator = seq.DocumentPaginator;
Casting this to your own paginator also will not work.
also that solution is very complex in case you have your own Paginator.
So I developed a simplified solution , which based on the accepted solution of this question and this worked exactly as needed.
// create your own paginator instead of this
// this is my specific own implementation for DocumentPaginator class
ReportPaginator paginator = new ReportPaginator(report);
Visual visual = paginator.GetPage(0).Visual; // first page - loop for all
RenderTargetBitmap bmp = new RenderTargetBitmap((int)paginator.PageSize.Width, (int)paginator.PageSize.Height, 96d, 96d, PixelFormats.Default);
bmp.Render(visual);
PngBitmapEncoder png = new PngBitmapEncoder();
png.Frames.Add(BitmapFrame.Create(bmp));
using (MemoryStream sm = new MemoryStream())
{
png.Save(sm);
return sm.ToArray();
}

Categories

Resources