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

3

6

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.

Scotland answered 27/9, 2011 at 1:4 Comment(0)
S
3

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

Scotland answered 3/10, 2011 at 0:39 Comment(0)
G
2
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);
}
Goggler answered 27/9, 2011 at 4:55 Comment(1)
Thanks for the help, unfortunately the serialising to string and back out within the clone method either doesn't work or it's simply taking a very very long time (I'm dealing with ~50k+ points)Scotland
D
1

I think these might help :

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

Exporting Chart as Image in WPF

Derward answered 27/9, 2011 at 4:12 Comment(1)
Thanks, unfortunately that covers all the same stuff that I've read everywhere else which just details how to export the element as the size that it's currently rendered in, where I want to be able to specify the size so I can give that control to the user (i.e. wants to export a big chart for a presentation)Scotland

© 2022 - 2024 — McMap. All rights reserved.