How to convert control.background(brush) to bitmap? - c#

How to convert system.windows.controls.control.background(brush) to bitmap?Thanks.

public BitmapSource 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);
return target;
}

Related

RenderTargetBitmap low image quality issue

I try to generate 300 dpi image using RenderTargetBitmap method.
When I try to use RenderTargetBitmap renderTargetBitmap = new RenderTargetBitmap(315, 195, 300, 300, PixelFormats.Pbgra32);
Image gets extremly big.
How to fix it?
RenderTargetBitmap renderTargetBitmap = new RenderTargetBitmap(315, 195, 96, 96, PixelFormats.Pbgra32);
renderTargetBitmap.Render(gridCard);
PngBitmapEncoder pngImage = new PngBitmapEncoder();
pngImage.Frames.Add(BitmapFrame.Create(renderTargetBitmap));
using (Stream fileStream = File.Create(path))
{
pngImage.Save(fileStream);
}
I need to scale the bitmap's size by the desired DPI (i.e. 300) divided by the default DPI (i.e. 96).
double w = 315;
double h = 195;
double dpi = 300;
double scale = dpi / 96;
RenderTargetBitmap renderTargetBitmap = new RenderTargetBitmap(
(int)(w * scale), (int)(h * scale), dpi, dpi, PixelFormats.Pbgra32);
UPDATE #1 Full solution (to print WPF Control is proxi-card to special printer MAGiCARD Enduro 3E)
private void BtnPrint_Click(object sender, RoutedEventArgs e)
{
try
{
var size = GetElementPixelSize(gridCard);
double w = size.Width;
double h = size.Height;
double dpiScale = 300.0 / 99.9;
double dpiX = 300.0;
double dpiY = 300.0;
RenderTargetBitmap renderTargetBitmap = new RenderTargetBitmap(Convert.ToInt32((w) * dpiScale), Convert.ToInt32((h) * dpiScale), dpiX, dpiY, PixelFormats.Pbgra32);
renderTargetBitmap.Render(gridCard);
PngBitmapEncoder pngImage = new PngBitmapEncoder();
pngImage.Frames.Add(BitmapFrame.Create(renderTargetBitmap));
var biRotated = new BitmapImage();
using (Stream fileStream = new MemoryStream())
{
pngImage.Save(fileStream);
fileStream.Seek(0, SeekOrigin.Begin);
biRotated.BeginInit();
biRotated.CacheOption = BitmapCacheOption.OnLoad;
biRotated.StreamSource = fileStream;
// biRotated.Rotation = Rotation.Rotate90; // if you need it
biRotated.EndInit();
}
var vis = new DrawingVisual();
var dc = vis.RenderOpen();
dc.DrawImage(biRotated, new Rect { Width = biRotated.Width, Height = biRotated.Height });
dc.Close();
var pdialog = new System.Windows.Controls.PrintDialog();
if (pdialog.ShowDialog() == true)
{
pdialog.PrintVisual(vis, "Proxy-card");
}
}
catch (Exception ex)
{
System.Windows.MessageBox.Show("Print error " + ex.Message);
}
}
// https://stackoverflow.com/questions/3286175/how-do-i-convert-a-wpf-size-to-physical-pixels
public Size GetElementPixelSize(UIElement element)
{
Matrix transformToDevice;
var source = PresentationSource.FromVisual(element);
if (source != null)
transformToDevice = source.CompositionTarget.TransformToDevice;
else
{
// IntPtr hWnd = source.Handle;
using (var source1 = new HwndSource(new HwndSourceParameters()))
{
transformToDevice = source1.CompositionTarget.TransformToDevice;
}
}
if (element.DesiredSize == new Size())
element.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
return (Size)transformToDevice.Transform((Vector)element.DesiredSize);
}

Export WPF ScrollViewer content as separate PNGs

In my WPF application I have a ScrollViewer which is dynamically populated by DataBinding and the use of a UserControl. Imagine that my UserControl is something simple that only contains a label, and the value displayed in the label comes from a list. So when I run the application it looks like below:
As you can see there are multiple instances of the UserControl each with a different value that is dynamically populated. My goal is to export each of the UserControls as a separate PNG. My EXPORT PNG button click should do this.
So I looked around and found this example which works fine for exporting the entire content of the ScorllViewer.
So I tried modifying it to achieve my goal, and I do get it to work to a certain extent, but not quite what I want.
Here's my code:
private void ExportPNG()
{
var dir = Directory.GetCurrentDirectory();
var file = "ITEM_{0}.PNG";
var height = 100.0; // Height of the UserControl
var width = mainSV.ActualWidth;
for (int i = 1; i <= ItemList.Count; i++)
{
var path = System.IO.Path.Combine(dir, string.Format(file, i));
Size size = new Size(width, height);
UIElement element = mainSV.Content as UIElement;
element.Measure(size);
element.Arrange(new Rect(new Point(0, 0), size));
RenderTargetBitmap renderTarget = new RenderTargetBitmap((int)width, (int)height, 96, 96, PixelFormats.Pbgra32);
VisualBrush sourceBrush = new VisualBrush(element);
DrawingVisual drawingVisual = new DrawingVisual();
DrawingContext drawingContext = drawingVisual.RenderOpen();
using (drawingContext)
{
drawingContext.DrawRectangle(sourceBrush, null, new Rect(new Point(0, 0), new Point(width, height)));
}
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);
}
}
}
Now this exports 5 PNGs (when my list has 5 items), bu they all contain the image of the first element:
And I figure the problem is likely where I do the drawingContext.DrawRectangle() because my rectanble coordinates are always the same. So I tried changing it to the following, which I thought should work, but for some reason it just generates one PNG with the first UserControl and 4 empty PNGs.
using (drawingContext)
{
drawingContext.DrawRectangle(sourceBrush, null, new Rect(new Point(0, (i-1) * height), new Point(width, height + ((i-1) * height))));
}
And the result:
What am I doing wrong here?
If you would like to run the code please find it here.
Please check below two methods. I tested it and it is working very well.
1. First, find children from you Items controls
2. Convert them to PNGs.
private void ExportPNG()
{
var dir = Directory.GetCurrentDirectory();
var file = "ITEM_{0}.PNG";
var height = 100.0;
var width = 100.0;
var children = GetChildrenOfType<UCDisplayItem>(itC);
foreach (var item in children)
{
var path = System.IO.Path.Combine(dir, string.Format(file, DateTime.Now.Ticks));
Size size = new Size(width, height);
UIElement element = item as UIElement;
element.Measure(size);
element.Arrange(new Rect(new Point(0, 0), size));
RenderTargetBitmap renderTarget = new RenderTargetBitmap((int)width, (int)height, 96, 96, PixelFormats.Pbgra32);
VisualBrush sourceBrush = new VisualBrush(element);
DrawingVisual drawingVisual = new DrawingVisual();
DrawingContext drawingContext = drawingVisual.RenderOpen();
using (drawingContext)
{
drawingContext.DrawRectangle(sourceBrush, null, new Rect(new Point(0, 0), new Point(width, height)));
//drawingContext.DrawRectangle(sourceBrush, null, new Rect(new Point(0, (i - 1) * height), new Point(width, height + ((i - 1) * height))));
}
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);
}
}
}
public List<T> GetChildrenOfType<T>( DependencyObject depObj)
where T : DependencyObject
{
var result = new List<T>();
if (depObj == null) return null;
var queue = new Queue<DependencyObject>();
queue.Enqueue(depObj);
while (queue.Count > 0)
{
var currentElement = queue.Dequeue();
var childrenCount = VisualTreeHelper.GetChildrenCount(currentElement);
for (var i = 0; i < childrenCount; i++)
{
var child = VisualTreeHelper.GetChild(currentElement, i);
if (child is T)
result.Add(child as T);
queue.Enqueue(child);
}
}
return result;
}
RESULT

Save image of whole ListView

I have the next code:
private static void SnapShotPNG(ListView source, string destination, int zoom)
{
try
{
double actualHeight = source.ActualHeight;
double actualWidth = source.ActualWidth;
double renderHeight = actualHeight * zoom;
double renderWidth = actualWidth * zoom;
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(zoom, zoom));
drawingContext.DrawRectangle(sourceBrush, null, new Rect(new Point(0, 0), new Point(actualWidth, actualHeight)));
}
renderTarget.Render(drawingVisual);
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(renderTarget));
using (FileStream stream = new FileStream(destination, FileMode.Create, FileAccess.Write))
{
encoder.Save(stream);
}
}
catch (Exception e)
{
MessageBox.Show(e.Message);
}
}
It saves a given source to an image, it works fine. But it save only visible part of control (in my case, only visible items of ListView). How can i save snapshot of whole items in ListView?
I've changed your first two lines, by adding Arrange and Measure methods, which allow the control to render in memory. I've assumed, that your control doesn't scroll horizontally and kept the width as it was, since otherwise it will use minimal width required for its largest child. You can change it.
Here is your method.
private static void SnapShotPNG(ListView source, string destination, int zoom)
{
try
{
double actualWidth = source.ActualWidth;
source.Measure(new Size(source.ActualWidth, Double.PositiveInfinity));
source.Arrange(new Rect(0, 0, actualWidth, source.DesiredSize.Height));
double actualHeight = source.ActualHeight;
double renderHeight = actualHeight * zoom;
double renderWidth = actualWidth * zoom;
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(zoom, zoom));
drawingContext.DrawRectangle(sourceBrush, null, new Rect(new Point(0, 0), new Point(actualWidth, actualHeight)));
}
renderTarget.Render(drawingVisual);
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(renderTarget));
using (FileStream stream = new FileStream(destination, FileMode.Create, FileAccess.Write))
{
encoder.Save(stream);
}
}
catch (Exception e)
{
MessageBox.Show(e.Message);
}
}

Snaps to pixel in RenderTargetBitmap

I write this code to draw a text in a RenderTargetBitmap:
DrawingVisual drawingVisual = new DrawingVisual();
using (DrawingContext drawingContext = drawingVisual.RenderOpen())
{
drawingContext.DrawText(new FormattedText("yes", CultureInfo.GetCultureInfo("en-us"),
FlowDirection.LeftToRight, new Typeface("Times New Roman"),
30, Brushes.Red), new Point(10, 10));
}
RenderTargetBitmap renderTargetBitmap = new RenderTargetBitmap(200, 200, 96, 96, PixelFormats.Pbgra32);
renderTargetBitmap.Render(drawingVisual);
image1.Source = renderTargetBitmap;//image1 is an Image control
the result is:
How can I remove this blurry effect? this effect comes from RenderTargetBitmap not from Image control.
You could use DrawingImage instead of RenderTargetBitmap
var drawingGroup = new DrawingGroup();
using (var drawingContext = drawingGroup.Open())
{
drawingContext.DrawText(
new FormattedText("yes",
CultureInfo.GetCultureInfo("en-us"),
FlowDirection.LeftToRight,
new Typeface("Times New Roman"),
30,
Brushes.Red),
new Point(10, 10));
}
image1.Source = new DrawingImage(drawingGroup);
You'll need to create DrawingGroup and open DrawingContext from there

How to save a non-fuzzy image of a control in WPF?

I am using a DrawingContext to draw images. I then render the result to a RenderTargetBitmap. I also render a Canvas to the same RenderTargetBitmap. Even though the pixel boundaries are crisp on screen, they become blurred and fuzzy when saved.
In the screenshot below, you can see the issue (with BitmapScalingMode = NearestNeighbor).
Here it is with BitmapScalingMode = HighQuality. It's smoother but not crisp and clean.
Here is the relevant section of my code. You can see that I tried setting the RenderOptions at multiple places but it seems to have no effect.
DrawingVisual drawingVisual = new DrawingVisual();
RenderTargetBitmap result = new RenderTargetBitmap((int)size.Width, (int)size.Height, 96, 96, PixelFormats.Pbgra32);
RenderOptions.SetBitmapScalingMode(drawingVisual, BitmapScalingMode.NearestNeighbor); // This forces the scaling to be on even-pixel boundaries
RenderOptions.SetBitmapScalingMode(drawCanvas, BitmapScalingMode.NearestNeighbor); // This forces the scaling to be on even-pixel boundaries
RenderOptions.SetBitmapScalingMode(result, BitmapScalingMode.NearestNeighbor); // This forces the scaling to be on even-pixel boundaries
using (DrawingContext context = drawingVisual.RenderOpen()) {
context.DrawRectangle(Brushes.Black, null, new Rect(new Point(), new Size(size.Width, size.Height)));
if (layers.Count >= 1 && layers[0].LayerImage != null && layers[0].LayerImage.Source != null && gridImage.Children[1].Visibility == System.Windows.Visibility.Visible)
context.DrawImage(layers[0].LayerImage.Source, new Rect(size)); // Draw first image.
context.Close();
}
result.Render(drawingVisual);
drawCanvas.Measure(drawCanvas.RenderSize);
drawCanvas.Arrange(new Rect(drawCanvas.RenderSize));
for (int i = 0; i < drawCanvas.Children.Count; i++) {
RenderOptions.SetBitmapScalingMode(drawCanvas.Children[i], BitmapScalingMode.NearestNeighbor); // This forces the scaling to be on even-pixel boundaries
}
result.Render(drawCanvas);
BitmapEncoder encoder = new PngBitmapEncoder();
if (result!= null) {
encoder.Frames.Add(BitmapFrame.Create((BitmapSource)result));
encoder.Save(fileStream);
}
I don't know if you have fixed that but that function works very good on my side.
public BitmapSource SnapShotPNG(UIElement source)
{
double actualWidth = source.RenderSize.Width;
double actualHeight = source.RenderSize.Height;
RenderTargetBitmap renderTarget = new RenderTargetBitmap((int)actualWidth, (int)actualHeight, 96, 96, PixelFormats.Pbgra32);
DrawingVisual visual = new DrawingVisual();
using (DrawingContext context = visual.RenderOpen())
{
VisualBrush sourceBrush = new VisualBrush(source);
context.DrawRectangle(sourceBrush, null, new Rect(new Point(0, 0), new Point(actualWidth, actualHeight)));
}
source.Measure(source.RenderSize); //Important
source.Arrange(new Rect(source.RenderSize)); //Important
renderTarget.Render(visual);
try
{
return new CroppedBitmap(renderTarget, new Int32Rect(0, 0, (int)actualWidth, (int)actualHeight));
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return null;
}
}
public static BitmapSource CaptureScreen(this UIElement visualElement, int? desiredLongestEdge = null)
{
double scale = 1;
if (desiredLongestEdge.HasValue)
{
if (visualElement.RenderSize.Width > visualElement.RenderSize.Height)
{
scale = desiredLongestEdge.Value/ visualElement.RenderSize.Width;
}
else
{
scale = desiredLongestEdge.Value / visualElement.RenderSize.Height ;
}
}
var targetBitmap =
new RenderTargetBitmap(
(int) Math.Ceiling(scale * (visualElement.RenderSize.Width + 1)),
(int) Math.Ceiling(scale * (visualElement.RenderSize.Height + 1)),
scale * 96,
scale * 96,
PixelFormats.Pbgra32);
visualElement.Measure(visualElement.RenderSize); //Important
visualElement.Arrange(new Rect(visualElement.RenderSize)); //Important
targetBitmap.Render(visualElement);
return targetBitmap;
}

Categories