Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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: chart

Which while big, isn't useful as it has nothing charted.

like image 672
Technicolour Avatar asked Sep 27 '11 01:09

Technicolour


3 Answers

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

like image 128
Technicolour Avatar answered Oct 12 '22 23:10

Technicolour


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);
}
like image 33
Muhammad Hasan Khan Avatar answered Oct 13 '22 00:10

Muhammad Hasan Khan


I think these might help :

Exporting Visifire Silverlight Chart as Image with a downloadable silverlight solution.

Exporting Chart as Image in WPF

like image 22
loxxy Avatar answered Oct 13 '22 01:10

loxxy