Snapshot of an WPF Canvas Area using RenderTargetBitmap - wpf

I want to create a Snapshot of the Canvas Area in my Application. I'm using Visual brush to get the Snapshot and saving the same using PngEncoder. But the resulting PNG is just a empty black image. I'm not sure the issue is with the BitmapSource created or the PNGEncoder issue. Here is the code I'm using to obtain the same.
public void ConvertToBitmapSource(UIElement element)
{
var target = new RenderTargetBitmap((int)(element.RenderSize.Width), (int)(element.RenderSize.Height), 96, 96, PixelFormats.Pbgra32);
var brush = new VisualBrush(element);
var visual = new DrawingVisual();
var drawingContext = visual.RenderOpen();
drawingContext.DrawRectangle(brush, null, new Rect(new Point(0, 0),
new Point(element.RenderSize.Width, element.RenderSize.Height)));
drawingContext.Close();
target.Render(visual);
PngBitmapEncoder encoder = new PngBitmapEncoder();
BitmapFrame outputFrame = BitmapFrame.Create(target);
encoder.Frames.Add(outputFrame);
using (FileStream file = File.OpenWrite("TestImage.png"))
{
encoder.Save(file);
}
}

Not sure why exactly your code isn't working. This works:
public void WriteToPng(UIElement element, string filename)
{
var rect = new Rect(element.RenderSize);
var visual = new DrawingVisual();
using (var dc = visual.RenderOpen())
{
dc.DrawRectangle(new VisualBrush(element), null, rect);
}
var bitmap = new RenderTargetBitmap(
(int)rect.Width, (int)rect.Height, 96, 96, PixelFormats.Default);
bitmap.Render(visual);
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmap));
using (var file = File.OpenWrite(filename))
{
encoder.Save(file);
}
}

Thank you both for the question and the answer.
For the benefit of the others looking for the same answer
I found that Clemens way leaves a black band in the image with the image shifted either down or right. As if it was not rendering the element at the correct position in the bitmap.
So I had to use the VisualBrush as Amar suggested.
Here is the code that worked for me:
RenderTargetBitmap RenderVisual(UIElement elt)
{
PresentationSource source = PresentationSource.FromVisual(elt);
RenderTargetBitmap rtb = new RenderTargetBitmap((int)elt.RenderSize.Width,
(int)elt.RenderSize.Height, 96, 96, PixelFormats.Default);
VisualBrush sourceBrush = new VisualBrush(elt);
DrawingVisual drawingVisual = new DrawingVisual();
DrawingContext drawingContext = drawingVisual.RenderOpen();
using (drawingContext)
{
drawingContext.DrawRectangle(sourceBrush, null, new Rect(new Point(0, 0),
new Point(elt.RenderSize.Width, elt.RenderSize.Height)));
}
rtb.Render(drawingVisual);
return rtb;
}

Related

WPF Rotate and return BitmapSource by any angle

Hei, I tried this:
public static BitmapSource RotateImage(Image b, float angle)
{
BitmapSource rotita = (BitmapSource)b.Source;
DrawingVisual drawingVisual = new DrawingVisual();
using (DrawingContext drawingContext = drawingVisual.RenderOpen())
{
var transform = new RotateTransform(angle);
drawingContext.PushTransform(transform);
drawingContext.DrawImage(rotita, new Rect(0,0, rotita.PixelWidth, rotita.PixelHeight));
drawingContext.Pop();
}
RenderTargetBitmap bmp = new RenderTargetBitmap(rotita.PixelWidth, rotita.PixelHeight, 96, 96, PixelFormats.Pbgra32);
bmp.Render(drawingVisual);
rotita = bmp;
return rotita;
}
But this does not work fine. I have this image at 0 degree and after rotation at 30 degrees this image.
What could I make the picture to be complete after rotation?
DrawingVisual drawingVisual = new DrawingVisual();
using (DrawingContext drawingContext = drawingVisual.RenderOpen())
{
drawingContext.DrawImage(back, new Rect(0, 0, imageWidth, imageHeight));
drawingContext.DrawImage(element, new Rect(x,y, elementWidth, elementHeight));
}
RenderTargetBitmap bmp = new RenderTargetBitmap(imageWidth, imageHeight, 96, 96, PixelFormats.Pbgra32);
bmp.Render(drawingVisual);
image.Source = bmp;
Element is the rotated image
The following method creates a composed bitmap from two others, where the second one is rotated around their common center point.
The two crucial parts of this method are the calculation of the transformed bounds of the rotated bitmap, and the alignment of the two bitmaps at their common center point.
private BitmapSource ComposeImage(
BitmapSource image1, BitmapSource image2, double rotationAngle)
{
var rotation = new RotateTransform(rotationAngle);
var size1 = new Size(image1.PixelWidth, image1.PixelHeight);
var size2 = new Size(image2.PixelWidth, image2.PixelHeight);
var center1 = new Vector(size1.Width / 2, size1.Height / 2);
var center2 = new Vector(size2.Width / 2, size2.Height / 2);
var rotatedSize = rotation.TransformBounds(new Rect(size2)).Size;
var totalSize = new Size(
Math.Max(size1.Width, rotatedSize.Width),
Math.Max(size1.Height, rotatedSize.Height));
var center = new Point(totalSize.Width / 2, totalSize.Height / 2);
rotation.CenterX = center.X;
rotation.CenterY = center.Y;
var dv = new DrawingVisual();
using (var dc = dv.RenderOpen())
{
dc.DrawImage(image1, new Rect(center - center1, size1));
dc.PushTransform(rotation);
dc.DrawImage(image2, new Rect(center - center2, size2));
}
var rtb = new RenderTargetBitmap(
(int)totalSize.Width, (int)totalSize.Height, 96, 96, PixelFormats.Default);
rtb.Render(dv);
return rtb;
}

How to render line to PNG bitmap i WPF?

I new to WPF and I am trying to render simple line to bitmap and save it to the PNG file. But I got empty Bitmap instead.
What I am doing wrong?
void RenderLineToFile()
{
var bitmap = RenderBitMap();
SaveImageToFile("image.png", bitmap);
}
RenderTargetBitmap RenderBitMap()
{
int bitmapWidth = 100;
int bitmapHeight = 100;
double dpiX = 72;
double dpiY = 72;
RenderTargetBitmap bm = new RenderTargetBitmap(bitmapWidth, bitmapHeight, dpiX, dpiY, PixelFormats.Pbgra32);
DrawingVisual drawing_visual = new DrawingVisual();
using (DrawingContext drawing_context = drawing_visual.RenderOpen())
{
Pen penBlack = new Pen(Brushes.Black, 1.0);
drawing_context.DrawLine(penBlack, new Point(0, 0), new Point(100, 100));
bm.Render(drawing_visual);
}
return bm;
}
public static void SaveImageToFile(string filePath, BitmapSource image)
{
using (var fileStream = new FileStream(filePath, FileMode.Create))
{
BitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(image));
encoder.Save(fileStream);
}
}
According to MSDN,
A DrawingContext must be closed before its content can be rendered...
Try taking bm.Render(drawing_visual); outside of the using clause.

Designing PDF with HTML in WPF application

Is it possible to design a pdf using HTML and js in a WPF application(C#). I tried using iTextSharp by taking a image of the control i want to export and then pasted in the pdf. It worked, but the problem is the control has to be visible. So, lets say if there is a listview in that control and that listview doesn't fits in one window, then whole listview doesn't gets exported. Any ideas how to approach?
This is so far I've done for exporting ListView to pdf.
ListView item = list;
double width = item.ActualWidth;
double height = item.ActualHeight;
Document doc = new Document(new iTextSharp.text.Rectangle(1200f, 700f));
String filePath;
string path = Environment.CurrentDirectory + "\\export\\" ;
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
filePath = path + "\\Test_.pdf";
PdfWriter.GetInstance(doc, new FileStream(filePath, FileMode.Append));
doc.Open();
RenderTargetBitmap bmpCopied = new RenderTargetBitmap((int)Math.Round(width + 100), (int)Math.Round(height + 50), 0, 0, PixelFormats.Default);
DrawingVisual drawingVisual = new DrawingVisual();
using (DrawingContext drawingContext = drawingVisual.RenderOpen())
{
item.Background = Brushes.White;
VisualBrush visualBrush = new VisualBrush(item);
drawingContext.DrawRectangle(Brushes.White, null, new Rect(new Point(), new Size(width + 100, height + 50)));
drawingContext.DrawRectangle(visualBrush, null, new Rect(new Point(), new Size(width + 100, height + 50)));
}
bmpCopied.Render(drawingVisual);
item.Background = Brushes.Transparent;
byte[] data;
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bmpCopied));
using (MemoryStream ms = new MemoryStream())
{
encoder.Save(ms);
data = ms.ToArray();
}
iTextSharp.text.Image pdfImage = iTextSharp.text.Image.GetInstance(data);
doc.Add(pdfImage);
doc.Close();
The pdf gets created, but opening the pdf shows error. Secondly, my ListView is scrollable, will all the items be shown in the pdf?. I just need to get it working. Any way would be ok for me

Add TransformGroup to a FramworkElement when rendering WPF to a PNG

I've got an app that turns some XAML Usercontrols into PNGs - this has worked really well up to now, unfortunately I now need to double the size of the images.
My method (that doesn't work!) was to add a ScaleTransform to the visual element after I've loaded it ...
This line is the new line at the top of the SaveUsingEncoder method.
visual.RenderTransform = GetScaleTransform(2);
The PNG is the new size (3000 x 2000) - but the XAML is Rendered at 1500x1000 in the centre of the image.
Can anyone assist please?
private void Load(string filename)
{
var stream = new FileStream(filename), FileMode.Open);
var frameworkElement = (FrameworkElement)(XamlReader.Load(stream));
var scale = 2;
var encoder = new PngBitmapEncoder();
var availableSize = new Size(1500 * scale, 1000 * scale);
frameworkElement.Measure(availableSize);
frameworkElement.Arrange(new Rect(availableSize));
name = name.Replace(" ", "-");
SaveUsingEncoder(frameworkElement, string.Format(#"{0}.png", name), encoder, availableSize);
}
private TransformGroup GetScaleTransform(int scale)
{
var myScaleTransform = new ScaleTransform {ScaleY = scale, ScaleX = scale};
var myTransformGroup = new TransformGroup();
myTransformGroup.Children.Add(myScaleTransform);
return myTransformGroup;
}
private void SaveUsingEncoder(FrameworkElement visual, string fileName, BitmapEncoder encoder, Size size)
{
visual.RenderTransform = GetScaleTransform(2);
var bitmap = new RenderTargetBitmap(
(int) size.Width,
(int) size.Height,
96,
96,
PixelFormats.Pbgra32);
bitmap.Render(visual);
var frame = BitmapFrame.Create(bitmap);
encoder.Frames.Add(frame);
using (var stream = File.Create(fileName))
{
encoder.Save(stream);
}
}
Called visual.UpdateLayout before rendering into the RenderTargetBitmap
(Thanks to Clemens for this answer - but he put it as a comment!)

Easiest way of saving wpf Image control to a file

I have a Image control inside my wpf application, which has a large image inside of it, but the control itself is only 60x150, this means it only shows a certain portion of this image.
What is the easiest way of saving the visible portion to a file?
Thank you for your help.
[EDIT]
I ended up using code found here (which I've not been able to locate before posting here)...
Grid r = new Grid();
r.Background = new ImageBrush(image2.Source);
System.Windows.Size sz = new System.Windows.Size(image2.Source.Width, image2.Source.Height);
r.Measure(sz);
r.Arrange(new Rect(sz));
RenderTargetBitmap rtb = new RenderTargetBitmap((int)image2.Source.Width, (int)image2.Source.Height, 96d, 96d, PixelFormats.Default);
rtb.Render(r);
BmpBitmapEncoder encoder = new BmpBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(rtb));
FileStream fs = File.Open(#"C:\lol.png", FileMode.Create);
encoder.Save(fs);
fs.Close();
You could use RenderTargetBitmap class and BitmapEncoder.
Define these methods:
void SaveToBmp(FrameworkElement visual, string fileName)
{
var encoder = new BmpBitmapEncoder();
SaveUsingEncoder(visual, fileName, encoder);
}
void SaveToPng(FrameworkElement visual, string fileName)
{
var encoder = new PngBitmapEncoder();
SaveUsingEncoder(visual, fileName, encoder);
}
// and so on for other encoders (if you want)
void SaveUsingEncoder(FrameworkElement visual, string fileName, BitmapEncoder encoder)
{
RenderTargetBitmap bitmap = new RenderTargetBitmap((int)visual.ActualWidth, (int)visual.ActualHeight, 96, 96, PixelFormats.Pbgra32);
bitmap.Render(visual);
BitmapFrame frame = BitmapFrame.Create(bitmap);
encoder.Frames.Add(frame);
using (var stream = File.Create(fileName))
{
encoder.Save(stream);
}
}
If you have your Image control inside a container like this:
<Grid x:Name="MyGrid">
<Image Name="MyImage" Stretch="None"></Image>
</Grid>
You just need to do so:
SaveToPng(MyGrid, "image.png");
Otherwise you can simply pass the dimensions you want when you use RenderTargetBitmap:
SaveToPng(MyImage, "image.png");
...
RenderTargetBitmap bitmap = new RenderTargetBitmap(YourWidth, YourHeight, 96, 96, PixelFormats.Pbgra32);
I ran into the same 'black' image issue that other did when using gliderkite's solution. The 'black' image appears to be due to the FrameworkElement's margin causing it to get rendered outside of the captured image. I found the workaround in a comment on Rick Stahl's blog
Specifically, measuring and arranging prior to rendering gives it a chance to adjust itself to the fact there are no margins in the picture. The following is a static class I now re-use for screen captures. This is based of of gliderkite's answer and the information on Rick Stahl's blog.
public static class ScreenCapture
{
public static void SaveToBmp(FrameworkElement visual, string fileName)
{
var encoder = new BmpBitmapEncoder();
SaveUsingEncoder(visual, fileName, encoder);
}
public static void SaveToPng(FrameworkElement visual, string fileName)
{
var encoder = new PngBitmapEncoder();
SaveUsingEncoder(visual, fileName, encoder);
}
public static void SaveToJpeg(FrameworkElement visual, string fileName)
{
var encoder = new JpegBitmapEncoder();
SaveUsingEncoder(visual, fileName, encoder);
}
private static void SaveUsingEncoder(FrameworkElement visual, string fileName, BitmapEncoder encoder)
{
RenderTargetBitmap bitmap = new RenderTargetBitmap((int)visual.ActualWidth, (int)visual.ActualHeight, 96, 96, PixelFormats.Pbgra32);
Size visualSize = new Size(visual.ActualWidth, visual.ActualHeight);
visual.Measure(visualSize);
visual.Arrange(new Rect(visualSize));
bitmap.Render(visual);
BitmapFrame frame = BitmapFrame.Create(bitmap);
encoder.Frames.Add(frame);
using (var stream = File.Create(fileName))
{
encoder.Save(stream);
}
}
}

Resources