WPF Screenshot JPG from UIElement with C# - c#

I'm trying to create a JPG from part of my WPF Applications. Like a screenshot, only of individual UIElements. I started here: http://www.grumpydev.com/2009/01/03/taking-wpf-screenshots/
I am using his extension method, which essential allows you to get a byte[] with UIElement.GetJpgImage(). This can then be written using a filestream to a JPG image. If I make a JPG of the whole window, it looks just fine! However, this is not ideal because it just captures what the user sees. Things that are not visible because of the scrollviewer or because their parent was animated to a small size won't show up.
If I take a "screenshot" of, say, a grid that I use for layout:
alt text http://img697.imageshack.us/img697/4233/fullscreenshot2.jpg
I get this crap with a black background. I don't want that. Furthermore, if I've collapsed this grid's height using animation, I won't get anything at all. Those are actually templated checkboxes, they should have black text above them, and the background of the grid should be white. Here's the code that someone else wrote to return the byte[] array that gets written to a filestream:
public static byte[] GetJpgImage(this UIElement source, double scale, int quality)
{
double actualHeight = source.RenderSize.Height;
double actualWidth = source.RenderSize.Width;
double renderHeight = actualHeight * scale;
double renderWidth = actualWidth * scale;
RenderTargetBitmap renderTarget = new RenderTargetBitmap((int) renderWidth, (int) renderHeight, 96, 96, PixelFormats.Pbgra32);
VisualBrush sourceBrush = new VisualBrush(source);
DrawingVisual drawingVisual = new DrawingVisual();
DrawingContext drawingContext = drawingVisual.RenderOpen();
using (drawingContext)
{
drawingContext.PushTransform(new ScaleTransform(scale, scale));
drawingContext.DrawRectangle(sourceBrush, null, new Rect(new Point(0, 0), new Point(actualWidth, actualHeight)));
}
renderTarget.Render(drawingVisual);
JpegBitmapEncoder jpgEncoder = new JpegBitmapEncoder();
jpgEncoder.QualityLevel = quality;
jpgEncoder.Frames.Add(BitmapFrame.Create(renderTarget));
Byte[] _imageArray;
using (MemoryStream outputStream = new MemoryStream())
{
jpgEncoder.Save(outputStream);
_imageArray = outputStream.ToArray();
}
return _imageArray;
}
Somewhere in there, we're getting a black background. Any insight?
EDIT: If I set the grid's background property to white, the screenshot comes out as expected. However, it's not feasible to set everything's background that I need to take a screenshot of.

Just a guess, I would think that a black background would represent portions of the byte array that are not set to anything in this process. The initial zeros in the array would appear as black.
To avoid this, I suggest initializing the array with 0xFF (byte.MaxValue) values.
UPDATED:
From looking at this closer, I think you should draw a white rectangle onto the image before you render the UI element. That ought to work anyway.
Just before this line of code
drawingContext.DrawRectangle(sourceBrush, null, new Rect(new Point(0, 0), new Point(actualWidth, actualHeight)));
put something like this
drawingContext.DrawRectangle(Brushes.White, null, new Rect(new Point(0, 0), new Point(actualWidth, actualHeight)));

Unfortunately the only thing that worked was just setting the element's background in the XAML. I didn't want to do this, but I guess it is what I need to do in this case. Thanks anyway for the suggestion.

Related

Take a screenshot from canvas with size of bigger than 20,000 pixels

I need take a screenshot from a part of Canvas but because RenderTargetBitmap
doesn't support take a screenshot from a region first I should take a screenshot from total of the Canvas and after that crop it with CroppedBitmap.
But problem is size of the canvas is bigger than 20000px and when I use from RenderTargetBitmap to take a screenshot sometimes I have an error about out of memory.
Do you have any idea to take a screenshot just with a start point and an end point instead of take a screenshot from total of my canvas in WPF?
You can render a cropped area from a UI element into a bitmap by a method like shown below.
It uses a VisualBrush with an appropriate Viewbox to draw the crop into a DrawingVisual, which is subsequently drawn into a RenderTargetBitmap.
private BitmapSource RenderCrop(Visual element, Rect crop)
{
var visualBrush = new VisualBrush
{
Visual = element,
ViewboxUnits = BrushMappingMode.Absolute,
Viewbox = crop,
Stretch = Stretch.None
};
var drawingVisual = new DrawingVisual();
using (var dc = drawingVisual.RenderOpen())
{
dc.DrawRectangle(visualBrush, null, new Rect(0, 0, crop.Width, crop.Height));
}
var bitmap = new RenderTargetBitmap(
(int)Math.Round(crop.Width), (int)Math.Round(crop.Height),
96, 96, PixelFormats.Default);
bitmap.Render(drawingVisual);
return bitmap;
}

WPF thumbnail image is blurry

I have a WPF app which saves out a thumbnail image as png. The code works well enough but when I open the image is very blurry. The image that it grabs comes from the canvas itself. The canvas changes its width and height depending on the image I'm loading. The desired thumbnail size will be 200 x 200 (pixels).
Here is my code
public void CreateThumbail(Canvas canvas, string filename)
{
RenderTargetBitmap rtb = new RenderTargetBitmap(
(int)canvas.ActualWidth,
(int)canvas.ActualHeight,
96, //dip X
96, //dpi Y
PixelFormats.Pbgra32);
rtb.Render(canvas);
PngBitmapEncoder pngImage = new PngBitmapEncoder();
pngImage.Frames.Add(CreateResizedImage(rtb, 200, 200, 0));
using (var filestream = System.IO.File.Create(filename))
{
pngImage.Save(filestream);
}
}
private static BitmapFrame CreateResizedImage(ImageSource source, int width, int height, int margin)
{
var rect = new Rect(margin, margin, width, height);
var group = new DrawingGroup();
RenderOptions.SetBitmapScalingMode(group, BitmapScalingMode.HighQuality);
group.Children.Add(new ImageDrawing(source, rect));
var drawingVisual = new DrawingVisual();
using (var drawingContext = drawingVisual.RenderOpen())
drawingContext.DrawDrawing(group);
var resizedImage = new RenderTargetBitmap(
(int)rect.Width, (int)rect.Height,// Resized dimensions
96, 96, // Default DPI values
PixelFormats.Pbgra32); // Default pixel format
resizedImage.Render(drawingVisual);
return BitmapFrame.Create(resizedImage);
}
I saved out the image before I resize it and it looks crisp and sharp. yet when I save out the thumbnail it's ugly and blurry. What am I doing wrong? Am I over-engineering this? Many thanks in advance.
Probably you need to respect the original dimensions of the image. For instance an image that is 400x400 will downscale to 200x200 quite nicely, but an image that is 235x235 will not.
This doesn't consider images that are not square to begin with.
You might try reducing the image height and width by a good factor (I would start by halving) repeatedly until the image is smaller than 200x200 and then padding it with white or transparent.
Image processing can be quite hard. It's not something I'm an expert in either so I'd probably try a 3rd party library like this one I just found on google: https://imageprocessor.org/

Rotate writeable bitmap without creating a new bitmap

Ok so here's how this works:
camera sends me an image as a writeable bitmap
client.WhenMasterFrameCaptured().ObserveOn(SynchronizationContext.Current).Subscribe(frame => UpdateMasterCameraPreview(frame));
I set that pointer to an image source(wpf Image control) in the method that this is subscribed to
if (cameraOneBitmap != null)
{
frame.Image.CopyTo(cameraOneBitmap);
}
else
{
cameraOneBitmap = frame.Image.ToWriteableBitmap();
}
cameraOneBitmap.Lock();
cameraOneBitmap.AddDirtyRect(new Int32Rect(0, 0, cameraOneBitmap.PixelWidth, cameraOneBitmap.PixelHeight));
cameraOneBitmap.Unlock();
cameraOneImage.Source = cameraOneBitmap;
The Problem: The method feeds me a bitmap that isn't the correct angle and i have no way of changing that. I must rotate it. I'm currently using a render transform in xaml. the problem is it shapes the image all weird in certain window sizes it hangs over the grid into the bottom of the window so i don't want to use that. I've tried using writeablebitmapex library but it creates a new bitmap and that gives me an out of memory exception in about 10 seconds... Is there a way to rotate this bitmap with out have to make a new one every time? Somehow the render transform does it without giving me problems. I was doing this to set the source but it still gives me an exception because it makes a new one.
public static RenderTargetBitmap RotateImage(double angle, WriteableBitmap sourceBitmap)
{
TransformedBitmap tb = new TransformedBitmap(sourceBitmap, new RotateTransform(angle));
DrawingVisual drawingVisual = new DrawingVisual();
DrawingContext drawingContext = drawingVisual.RenderOpen();
drawingContext.DrawImage(tb, new Rect(0, 0, tb.PixelWidth, tb.PixelHeight));
//drawingContext.PushTransform(new RotateTransform(270, .5, .5));
drawingContext.Close();
System.Windows.Media.Imaging.RenderTargetBitmap bmp = new System.Windows.Media.Imaging.RenderTargetBitmap(tb.PixelWidth, tb.PixelHeight, 96, 96, PixelFormats.Pbgra32);
bmp.Render(drawingVisual);
return bmp;
}

Saving a WPF visual element as JPEG

This thing has been driving me crazy.
I have a Visiblox chart. which I'm currently exporting as a PNG using the following code:
var chart = this.CalibrationChartVisibility == Visibility.Visible ? this.calibrationChart : this.residualChart;
var transform = chart.LayoutTransform;
chart.LayoutTransform = null;
var width = (int)chart.ActualWidth;
var height = (int)chart.ActualHeight;
var rtb = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
rtb.Render(chart);
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(rtb));
var stream = new MemoryStream();
encoder.Save(stream);
stream.Position = 0;
chart.LayoutTransform = transform;
return stream.ToArray();
and I get something like this:
But now I need to also need to export it as a JPEG. I thought it would be simple, just change the encoder but this is what I get:
I've tried this:
http://social.msdn.microsoft.com/Forums/vstudio/en-US/31ac62d4-399b-4f2e-a9b9-749efe7528b6/rendertargetbitmap-to-file-problem?forum=wpf
and this:
http://www.grumpydev.com/2009/01/03/taking-wpf-screenshots/
and this:
Get a bitmap image from a Control view
and ervey sugestion on this post:
How to save image using JpegBitmapEncoder
or this one:
saving WPF InkCanvas to a JPG - image is getting cropped
and everything else which crossed my mind, but the outcome is still the same.
There must be something I'm overlooking but I have no idea what it is.
To sum up comments this seems to be a background issue as PNG, attached to this question, has everything transparent apart from chart lines and since JPEG does not support transparency all that is transparent will be black.
Simpliest solution would be to set background of chart to some color
Disclaimer: I provided this answer for the System.Drawing.Image from ImageSource in Resources question and was going to vote to close this question as a duplicate of the other, but couldn't because the question author didn't accept the answer.
In WPF, every UI element extends the Visual Class which Provides rendering support in WPF. There is also a RenderTargetBitmap Class that has a Render Method that takes a Visual object as an input parameter. So you could set your ImageSource as the Source property of an Image and simply render the Image to a Bitmap image:
Image yourImageObject = new Image();
yourImageObject.Source = yourImageSource;
RenderTargetBitmap renderTargetBitmap =
new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Default);
renderTargetBitmap.Render(yourImageObject);
// Save to .png file
PngBitmapEncoder pngBitmapEncoder = new PngBitmapEncoder();
pngBitmapEncoder.Frames.Add(BitmapFrame.Create(renderTargetBitmap));
using (Stream stream = File.Create(filepath))
{
pngBitmapEncoder.Save(stream);
}
As this is well documented on the internet, I won't bother to repeat the whole story here. To find out the full story, please see the How to Render Bitmap or to Print a Visual in WPF page from the Dot NET Tricks website, which will also help you with your printing requirement.
UPDATE >>>
Ok, so most of this applies to you in just the same way, except that you'd want to use a JpegBitmapEncoder object instead. This example from the linked page shows another way to save a JPEG image:
int width = 128;
int height = width;
int stride = width / 8;
byte[] pixels = new byte[height * stride];
// Define the image palette
BitmapPalette myPalette = BitmapPalettes.Halftone256;
// Creates a new empty image with the pre-defined palette
BitmapSource image = BitmapSource.Create(
width,
height,
96,
96,
PixelFormats.Indexed1,
myPalette,
pixels,
stride);
FileStream stream = new FileStream("new.jpg", FileMode.Create);
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
TextBlock myTextBlock = new TextBlock();
myTextBlock.Text = "Codec Author is: " + encoder.CodecInfo.Author.ToString();
encoder.FlipHorizontal = true;
encoder.FlipVertical = false;
encoder.QualityLevel = 30;
encoder.Rotation = Rotation.Rotate90;
encoder.Frames.Add(BitmapFrame.Create(image));
encoder.Save(stream);
Please let me know if you have any problems.

image quality is blurry

I have a situation where I want to convert some XAML to an image, so I created a RichTextBox and then took the image of it. Now problem is that words in image is blurred, any idea how I might be able to fix it?
public System.Drawing.Bitmap ConvertXamltoImage(string XamlString, int Width, int Height)
{
RichTextBox AdContentRichTextBox = new RichTextBox() { Width = Width, Height = Height };
AdContentRichTextBox.BorderThickness = new Thickness(0);
XmlReader _XmlReader = XmlReader.Create(new StringReader(XamlString));
AdContentRichTextBox.Document = XamlString;
var size = new Size(Width, Height);
AdContentRichTextBox.Measure(size);
AdContentRichTextBox.Arrange(new Rect(size));
RenderTargetBitmap bmp = new RenderTargetBitmap(Width, Height, 300, 300, PixelFormats.Pbgra32);
bmp.Render(AdContentRichTextBox);
DrawingVisual _drawingVisual = new DrawingVisual();
using (DrawingContext _drwaingContext = _drawingVisual.RenderOpen())
{
VisualBrush _visualBrush = new VisualBrush(AdContentRichTextBox);
}
PngBitmapEncoder _png = new PngBitmapEncoder();
_png.Frames.Add(BitmapFrame.Create(bmp));
System.Drawing.Bitmap _tempBitmap = null;
using (Stream _fileStream = new MemoryStream())
{
_png.Save(_fileStream);
_tempBitmap = new System.Drawing.Bitmap(_fileStream);
_fileStream.Flush();
}
return _tempBitmap;
}
Hmmmm..there could be lots of things all interacting here:
1st
"Grayscale fall back - if ClearType is disabled or one is rendering text in certain situations where the ClearType algorithm cannot be run, WPF will use a grayscale rendering algorithm to antialias the rendered text."
Rendering Text to a RenderTargetBitmap seems to be one of those situations....(the renderer switches from a hardware to a software path).
2nd
In addition NET 4 switched the default scaling algorithm from high-quality (Fant) to low-quality (Bi-Linear).....now that shouldn't come into play here as it doesn't look like you are scaling the bitmap in any way...but you never know what's going on inside. It's possible to switch the scaler back to the higher quality one.
http://www.olsonsoft.com/blogs/stefanolson/post/Workaround-for-low-quality-bitmap-resizing-in-WPF-4.aspx
3rd
You may need to take into account the parent container of the RichTextBox...see last link below, mentions it can distort the font rendering.
Problems with rendering text as bitmaps using WPF
Some ideas on how to work around this are:
render the RichTextBox at a higher resolution e.g. 600dpi, and then scale down the bitmap (probably will make no difference)
capture the screen....difficult or not practical if your visual is offscreen/obscured, etc.
See related links:
http://windowsclient.net/wpf/white-papers/wpftextclarity.aspx
WPF RenderTargetBitmap downscaling text ClearType to GreyScale
WPF RenderTargetBitmap downscaling TextRenderMode to GreyScale
WPF text rendering inconsistencies

Categories

Resources