Generate Png from UserControl in background thread - c#

I'm trying to generate png images from a background thread to prevent the UI from freezing up. I have a UserControl that I'm using to generate the visual. The problem is that it takes about 200ms per image, and I am unable to get it to run in the background.
To be clear I do not need to show the anything in the UI at all only generate a png to be stored in a database.
Here is the code that I tried putting in a task.
var c1 = await Task<byte[]>.Factory.StartNew(() =>
{
var _TrimDesignerVM = new TrimDesignerVM(new Profile(line.CustomTrimInfo));
_TrimDesignerVM.CanvasWidth = 520.0;
_TrimDesignerVM.CanvasHeight = 520.0;
_TrimDesignerVM.ZoomToFit();
//System.InvalidOperationException: 'The calling thread must be STA, because many UI components require this.'
var control = new Views.CustomTrimDetail(); //this is the line that throws an exception.
control.DataContext = _TrimDesignerVM;
control.Width = 742;
control.Height = 520;
control.Measure(new Size(742, 520));
control.Arrange(new Rect(new Size(742, 520)));
control.UpdateLayout();
var rect = new Rect(control.RenderSize);
var visual = new DrawingVisual();
using (var dc = visual.RenderOpen()) dc.DrawRectangle(new VisualBrush(control), null, rect);
var c = new byte[] { };
double w = 742, h = 520, dpi = 96, scale = dpi / 96;
var bitmap = new RenderTargetBitmap((int)(w * scale), (int)(h * scale), dpi, dpi, PixelFormats.Default);
bitmap.Render(visual);
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmap));
using (MemoryStream ms = new MemoryStream())
{
encoder.Save(ms);
contents = ms.ToArray();
}
return c;
});
Is it possible to do this without freezing the UI thread?

Related

Export chart to PNG without rendering it in the UI - WPF LiveCharts2

I'm trying to export a chart as a PNG file (without having to render it in the UI) and have implemented the following code for LiveCharts v0 which works fine...
LiveCharts.Wpf.CartesianChart control = new LiveCharts.Wpf.CartesianChart()
{
// Series = ...
Width = 700,
Height = 700,
DisableAnimations = true
};
var viewbox = new Viewbox();
viewbox.Child = control;
viewbox.Measure(control.RenderSize);
viewbox.Arrange(new Rect(new System.Windows.Point(0, 0), control.RenderSize));
control.Update(true, true); //force chart redraw
viewbox.UpdateLayout();
var encoder = new PngBitmapEncoder();
var bitmap = new RenderTargetBitmap((int)control.ActualWidth, (int)control.ActualHeight, 96, 96, PixelFormats.Pbgra32);
bitmap.Render(control);
var frame = BitmapFrame.Create(bitmap);
encoder.Frames.Add(frame);
using (Stream stm = File.Create("filename.png"))
encoder.Save(stm);
However, when I try to implement this for LiveCharts2 with the following code, I'm not seeing any chart output as a PNG...
CartesianChart visual = new CartesianChart() {
// Series = series,
Height = 700,
Width = 700,
EasingFunction = null
};
var viewbox2 = new Viewbox();
viewbox2.Child = visual;
viewbox2.Measure(visual.RenderSize);
viewbox2.Arrange(new Rect(new System.Windows.Point(0, 0), visual.RenderSize));
visual.CoreChart.Update(new LiveChartsCore.Kernel.ChartUpdateParams() { IsAutomaticUpdate = false, Throttling = false });
viewbox2.UpdateLayout();
var encoder2 = new PngBitmapEncoder();
RenderTargetBitmap bmp = new(700, 700, 96, 96, PixelFormats.Pbgra32);
bmp.Render(visual);
encoder2.Frames.Add(BitmapFrame.Create(bmp));
using (Stream stm = File.Create("filename.png"))
encoder2.Save(stm);
Can anyone help fix this issue? Thanks in advance.
Found the answer here:
https://github.com/beto-rodriguez/LiveCharts2/discussions/725#discussioncomment-4030202
var skChart = new SKCartesianChart(chartControl) { Width = 900, Height = 600, };
skChart.SaveImage("CartesianImageFromControl.png");

What is a quick way to scale down an image using SharpDX hardware acceleration in C#?

I'm trying to scale down large images (~ 23k x 1k) to be displayed in winforms. The current way I'm scaling the images is taking too long, which is why I want to use the GPU through SharpDX (C#) to improve performance. What would be a good way to do this?
I'm working on a method to scale an image by applying the scale effect (that I don't have access to right now), but I still don't fully understand SharpDX, so I'm wondering if there's a better way to go about this. I modeled my code off of this example but I removed the text overlay, the image saving, the drawing portion, and I replaced the gaussian with the scaling effect. Since I'm using GDI to do the drawing for simplicity, the image is in the form of a systems drawing bitmap so I initialize the encoder with a memory stream that I use to get the output image after the scaling effect is applied. The smaller tests I have done with this method don't seem to make the scaling much quicker, but I haven't been able to put this fully in action yet.
Is there a quicker way to scale down an image using SharpDX, or is something along the lines of my current method the quickest?
Based on what I found on https://csharp.hotexamples.com/examples/SharpDX.WIC/WICStream/-/php-wicstream-class-examples.html
Looks like SharpDX about twice the performance of GDI or better.
Test code that works on my Windows 11 computer. Should be enough to get you started even if you know as little of SharpDX as I do.
var inputPath = #"x:\Temp\1\_Landscape.jpg";
var data = File.ReadAllBytes(inputPath);
var sw = Stopwatch.StartNew();
var iu6 = new ImageUtilities6();
Debug.WriteLine($"Init: {sw.ElapsedMilliseconds}ms total");
for (int i = 0; i < 10; i++)
{
sw.Restart();
var image = iu6.ResizeImage(data, 799, 399);
Debug.WriteLine($"Resize: {sw.ElapsedMilliseconds}ms total");
File.WriteAllBytes(#"X:\TEMP\1\007-xxx.jpg", image);
}
sw.Restart();
iu6.Dispose();
Debug.WriteLine($"Dispose: {sw.ElapsedMilliseconds}ms total");
Class I made based on the samples found on that page.
using SharpDX;
using dw = SharpDX.DirectWrite;
using d2 = SharpDX.Direct2D1;
using d3d = SharpDX.Direct3D11;
using dxgi = SharpDX.DXGI;
using wic = SharpDX.WIC;
using System;
using System.IO;
using SharpDX.Direct3D11;
using SharpDX.WIC;
using SharpDX.DirectWrite;
namespace SharpDX_ImageResizingTest
{
public class ImageUtilities6 : IDisposable
{
private Device defaultDevice;
private Device1 d3dDevice;
private dxgi.Device dxgiDevice;
private d2.Device d2dDevice;
private ImagingFactory2 imagingFactory;
//private d2.DeviceContext d2dContext;
private Factory dwFactory;
private d2.PixelFormat d2PixelFormat;
public ImageUtilities6()
{
//SharpDX.Configuration.EnableObjectTracking = true; //Turn on memory leak logging
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// INITIALIZATION ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// initialize the D3D device which will allow to render to image any graphics - 3D or 2D
defaultDevice = new SharpDX.Direct3D11.Device(SharpDX.Direct3D.DriverType.Hardware,
d3d.DeviceCreationFlags.VideoSupport
| d3d.DeviceCreationFlags.BgraSupport
| d3d.DeviceCreationFlags.Debug); // take out the Debug flag for better performance
d3dDevice = defaultDevice.QueryInterface<d3d.Device1>(); // get a reference to the Direct3D 11.1 device
dxgiDevice = d3dDevice.QueryInterface<dxgi.Device>(); // get a reference to DXGI device
//var dxgiSurface = d3dDevice.QueryInterface<dxgi.Surface>(); // get a reference to DXGI surface
d2dDevice = new d2.Device(dxgiDevice); // initialize the D2D device
imagingFactory = new wic.ImagingFactory2(); // initialize the WIC factory
dwFactory = new dw.Factory();
// specify a pixel format that is supported by both D2D and WIC
d2PixelFormat = new d2.PixelFormat(dxgi.Format.R8G8B8A8_UNorm, d2.AlphaMode.Premultiplied);
// if in D2D was specified an R-G-B-A format - use the same for wic
}
public byte[] ResizeImage(byte[] image, int targetWidth, int targetHeight)
{
int dpi = 72; //96? does it even matter
var wicPixelFormat = wic.PixelFormat.Format32bppPRGBA;
// initialize the DeviceContext - it will be the D2D render target and will allow all rendering operations
var d2dContext = new d2.DeviceContext(d2dDevice, d2.DeviceContextOptions.None);
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// IMAGE LOADING ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
var imageStream = new MemoryStream(image);
//var decoder = new wic.PngBitmapDecoder(imagingFactory); // we will load a PNG image
var decoder = new wic.JpegBitmapDecoder(imagingFactory); // we will load a JPG image
var inputStream = new wic.WICStream(imagingFactory, imageStream); // open the image for reading
decoder.Initialize(inputStream, wic.DecodeOptions.CacheOnLoad);
// decode the loaded image to a format that can be consumed by D2D
var formatConverter = new wic.FormatConverter(imagingFactory);
var frame = decoder.GetFrame(0);
formatConverter.Initialize(frame, wicPixelFormat);
// load the base image into a D2D Bitmap
var inputBitmap = d2.Bitmap1.FromWicBitmap(d2dContext, formatConverter, new d2.BitmapProperties1(d2PixelFormat));
// store the image size - output will be of the same size
var inputImageSize = formatConverter.Size;
var pixelWidth = inputImageSize.Width;
var pixelHeight = inputImageSize.Height;
// Calculate correct aspect ratio
double aspectRatio = (double)pixelHeight / (double)pixelWidth;
double targetAspectRatio = (double)targetHeight / (double)targetWidth;
if (targetAspectRatio > aspectRatio)
{
targetHeight = (int)(targetHeight * (aspectRatio / targetAspectRatio));
}
else
{
targetWidth = (int)(targetWidth * (targetAspectRatio / aspectRatio));
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// EFFECT SETUP ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//Effect 1 : BitmapSource - take decoded image data and get a BitmapSource from it
//var bitmapSourceEffect = new d2.Effects.BitmapSource(d2dContext);
//bitmapSourceEffect.WicBitmapSource = formatConverter;
// Effect 2 : GaussianBlur - give the bitmapsource a gaussian blurred effect
//var gaussianBlurEffect = new d2.Effects.GaussianBlur(d2dContext);
//gaussianBlurEffect.SetInput(0, bitmapSourceEffect.Output, true);
//gaussianBlurEffect.StandardDeviation = 5f;
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// RENDER TARGET SETUP ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// create the d2d bitmap description using default flags (from SharpDX samples) and 96 DPI
var d2dBitmapProps = new d2.BitmapProperties1(d2PixelFormat, 96, 96, d2.BitmapOptions.Target | d2.BitmapOptions.CannotDraw);
// the render target
var d2dRenderTarget = new d2.Bitmap1(d2dContext, new Size2(targetWidth, targetHeight), d2dBitmapProps);
d2dContext.Target = d2dRenderTarget; // associate bitmap with the d2d context
d2dContext.BeginDraw();
//d2dContext.DrawImage(bitmapSourceEffect); //Way #1
//d2dContext.DrawImage(gaussianBlurEffect); //Way #2
//d2dContext.DrawBitmap(inputBitmap, 1, d2.InterpolationMode.Linear); //Way #3
d2dContext.DrawBitmap(inputBitmap, new SharpDX.Mathematics.Interop.RawRectangleF(0, 0, targetWidth, targetHeight), 1, d2.InterpolationMode.Linear, null, null); //Way #4 - resizing
d2dContext.EndDraw();
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// IMAGE SAVING ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// delete the output file if it already exists
//if (System.IO.File.Exists(outputPath)) System.IO.File.Delete(outputPath);
// use the appropiate overload to write either to stream or to a file
var outputStream = new MemoryStream();
var stream = new wic.WICStream(imagingFactory, outputStream);
// select the image encoding format HERE
var encoder = new wic.JpegBitmapEncoder(imagingFactory);
encoder.Initialize(stream);
var bitmapFrameEncode = new wic.BitmapFrameEncode(encoder);
bitmapFrameEncode.Options.ImageQuality = 0.95f;
bitmapFrameEncode.Initialize();
bitmapFrameEncode.SetSize(targetWidth, targetHeight);
bitmapFrameEncode.SetPixelFormat(ref wicPixelFormat);
// this is the trick to write D2D1 bitmap to WIC
var imageEncoder = new wic.ImageEncoder(imagingFactory, d2dDevice);
imageEncoder.WriteFrame(d2dRenderTarget, bitmapFrameEncode, new wic.ImageParameters(d2PixelFormat, dpi, dpi, 0, 0, targetWidth, targetHeight));
bitmapFrameEncode.Commit();
encoder.Commit();
imageEncoder.Dispose();
bitmapFrameEncode.Dispose();
encoder.Dispose();
stream.Dispose();
formatConverter.Dispose();
d2dRenderTarget.Dispose();
inputStream.Dispose();
decoder.Dispose();
inputBitmap.Dispose();
frame.Dispose();
d2dContext.Dispose();
return outputStream.ToArray();
}
public void Dispose()
{
//bitmapSourceEffect.Dispose();
dwFactory.Dispose();
imagingFactory.Dispose();
d2dDevice.Dispose();
dxgiDevice.Dispose();
d3dDevice.Dispose();
defaultDevice.Dispose();
//System.Diagnostics.Debug.WriteLine(SharpDX.Diagnostics.ObjectTracker.ReportActiveObjects()); Log that memory leak
}
public byte[] ResizeImage1(byte[] data, int width, int height)
{
var ms = new MemoryStream(data);
//Image image = Image.FromStream(ms);
System.Drawing.Image image = System.Drawing.Image.FromStream(ms, false, false);
System.Drawing.Bitmap result = new System.Drawing.Bitmap(width, height);
// set the resolutions the same to avoid cropping due to resolution differences
result.SetResolution(image.HorizontalResolution, image.VerticalResolution);
//use a graphics object to draw the resized image into the bitmap
using (System.Drawing.Graphics graphics = System.Drawing.Graphics.FromImage(result))
{
//set the resize quality modes to high quality
graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
//draw the image into the target bitmap
graphics.DrawImage(image, 0, 0, result.Width, result.Height);
}
var stream = new System.IO.MemoryStream();
image.Save(stream, System.Drawing.Imaging.ImageFormat.Jpeg);
stream.Position = 0;
return stream.ToArray();
}
}
}
Library used was SharpDX + .Direct2D1 + .Direct3D11 + .DXGI 4.2.

WPF rendering control to Bitmap without Window

So we have a WPF application that shows some charts created using the Live Charts library. However I got the task to render the charts in the background, and save them to a PNG image without displaying a WPF window. I found an article online that takes a UserControl and does exactly that, but trying it myself I only get a transparent image of 80 by 50 pixels.
Unfortunately I am not very familiar with the WPF internals of rendering, so any help is really appreciated. This is my code rendered down to the bare minimum with the simplest chart example from LiveChart itself.
using System;
using System.IO;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using LiveCharts.Wpf;
using LiveCharts.Geared;
using LiveCharts;
namespace Screenshot2Pdf {
class Program {
[STAThread]
static void Main(string[] args) {
var Series = new SeriesCollection();
var r = new Random();
for (var i = 0; i < 30; i++) // 30 series
{
var trend = 0d;
var values = new double[10000];
for (var j = 0; j < 10000; j++) // 10k points each
{
trend += (r.NextDouble() < .8 ? 1 : -1) * r.Next(0, 10);
values[j] = trend;
}
var series = new LineSeries {
Values = values.AsGearedValues().WithQuality(Quality.Low),
Fill = Brushes.Transparent,
StrokeThickness = .5,
PointGeometry = null //use a null geometry when you have many series
};
Series.Add(series);
}
var chart = new CartesianChart();
chart.Series = Series;
chart.BeginInit();
chart.EndInit();
// Measure and arrange the tile
chart.Measure(new System.Windows.Size {
Height = double.PositiveInfinity,
Width = double.PositiveInfinity
});
chart.Arrange(new System.Windows.Rect(0, 0,
chart.DesiredSize.Width,
chart.DesiredSize.Height));
chart.UpdateLayout();
RenderTargetBitmap rtb = new RenderTargetBitmap((int)chart.DesiredSize.Width, (int)chart.DesiredSize.Height, 96, 96, PixelFormats.Pbgra32);
rtb.Render(chart);
PngBitmapEncoder png = new PngBitmapEncoder();
png.Frames.Add(BitmapFrame.Create(rtb));
MemoryStream stream = new MemoryStream();
png.Save(stream);
var image = System.Drawing.Image.FromStream(stream);
image.Save(#"D:\bitmap.png");
}
}
}
Oké I am not sure why. As said before I am not very familiar with WPF's internal rendering mechanism and what triggers it, but the solution is to wrap the Chart in a Viewbox. The following code does the trick:
var viewbox = new Viewbox();
viewbox.Child = chart;
viewbox.Measure(chart.RenderSize);
viewbox.Arrange(new Rect(new Point(0, 0), chart.RenderSize));
chart.Update(true, true); //force chart redraw
viewbox.UpdateLayout();
// RenderTargetBitmap rtb = new RenderTargetBitmap((int)chart.DesiredSize.Width, (int)chart.DesiredSize.Height, 96, 96, PixelFormats.Pbgra32);
var rtb = new RenderTargetBitmap(500, 500, 96, 96, PixelFormats.Default);
rtb.Render(chart);
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(rtb));
using (var stream = File.OpenWrite(#"D:\bitmap.png")) {
encoder.Save(stream);
}

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

Saving an image of a UIElement rendered larger than it appears on screen

I have a chart that I'm displaying to the user and I want to be able to export the chart as an image to disk so they can use it outside of the application (for a presentation or something).
I've managed to get the basic idea working using PngBitmapEncoder and RenderTargetBitmap but the image I get out of it is way to small to really be usable and I want to get a much larger image.
I tried to simply increase the Height and Width of the control I was wanting to render, but the parent seems to have direct control on the render size. From this I tried to duplicate the UIElement in memory but then the render size was (0,0) and when I tried to use methods to get it to render, such as Measure() Arrange() and UpdateLayout() they throw exceptions about needing to decouple the parent to call these, but as it's in memory and not rendered there shouldn't be a parent?
This is all done with Visiblox charting API
Here is what I've got currently, except it doesn't work :(
var width = 1600;
var height = 1200;
var newChart = new Chart { Width = width, Height = height, Title = chart.Title, XAxis = chart.XAxis, YAxis = chart.YAxis, Series = chart.Series};
Debug.WriteLine(newChart.RenderSize);
var size = new Size(width, height);
newChart.Measure(size);
newChart.Arrange(new Rect(size));
newChart.UpdateLayout();
Debug.WriteLine(newChart.RenderSize);
var rtb = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
rtb.Render(newChart);
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(rtb));
using (var stream = fileDialog.OpenFile())
encoder.Save(stream);
I've gotten a bit closer, it now render the graph background the axis' etc. but just not the actual lines that are being graphed. Below is an updated source
public static void RenderChartToImage(Chart elementToRender, string filename)
{
if (elementToRender == null)
return;
Debug.Write(elementToRender.RenderSize);
var clone = new Chart();
clone.Width = clone.Height = double.NaN;
clone.HorizontalAlignment = HorizontalAlignment.Stretch;
clone.VerticalAlignment = VerticalAlignment.Stretch;
clone.Margin = new Thickness();
clone.Title = elementToRender.Title;
clone.XAxis = new DateTimeAxis();
clone.YAxis = new LinearAxis() { Range = Range<double>)elementToRender.YAxis.Range};
foreach (var series in elementToRender.Series)
{
var lineSeries = new LineSeries
{
LineStroke = (series as LineSeries).LineStroke,
DataSeries = series.DataSeries
};
clone.Series.Add(lineSeries);
}
var size = new Size(1600, 1200);
clone.Measure(size);
clone.Arrange(new Rect(size));
clone.UpdateLayout();
Debug.Write(clone.RenderSize);
var height = (int)clone.ActualHeight;
var width = (int)clone.ActualWidth;
var renderer = new RenderTargetBitmap(width, height, 96d, 96d, PixelFormats.Pbgra32);
renderer.Render(clone);
var pngEncoder = new PngBitmapEncoder();
pngEncoder.Frames.Add(BitmapFrame.Create(renderer));
using (var file = File.Create(filename))
{
pngEncoder.Save(file);
}
}
So I get out something like this:
Which while big, isn't useful as it has nothing charted.
http://www.visiblox.com/blog/2011/05/printing-visiblox-charts
The main point I was missing was
InvalidationHandler.ForceImmediateInvalidate = true;
Setting this before I rendered the chart in memory and then reverting it once I had finished. From there it was smooth sailing :D
RenderTargetBitmap DrawToImage<T>(T source, double scale) where T:FrameworkElement
{
var clone = Clone(source);
clone.Width = clone.Height = Double.NaN;
clone.HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch;
clone.VerticalAlignment = System.Windows.VerticalAlignment.Stretch;
clone.Margin = new Thickness();
var size = new Size(source.ActualWidth * scale, source.ActualHeight * scale);
clone.Measure(size);
clone.Arrange(new Rect(size));
var renderBitmap = new RenderTargetBitmap((int)clone.ActualWidth, (int)clone.ActualHeight, 96, 96, PixelFormats.Pbgra32);
renderBitmap.Render(clone);
return renderBitmap;
}
static T Clone<T>(T source) where T:UIElement
{
if (source == null)
return null;
string xaml = XamlWriter.Save(source);
var reader = new StringReader(xaml);
var xmlReader = XmlTextReader.Create(reader, new XmlReaderSettings());
return (T)XamlReader.Load(xmlReader);
}
I think these might help :
Exporting Visifire Silverlight Chart as Image with a downloadable silverlight solution.
Exporting Chart as Image in WPF

Categories