This is a migrated thread and some comments may be shown as answers.

low resolution using ExportToImage on big diagram

6 Answers 409 Views
Diagram
This is a migrated thread and some comments may be shown as answers.
Claudio B.
Top achievements
Rank 1
Claudio B. asked on 08 May 2016, 11:11 AM

Hi,

I'm using RadDiagramView on big diagram and I need to export png images.

png encoding and 91 dpi is ok for my needs, so I'm calling ExportToImage method leaving default parameters as follows:

using (var stream = File.Open($"{filename}.png", FileMode.Create))
{
   radDiagram.ExportToImage(stream);
}

On big diagrams, i'm in trouble because the quality of exported images is very low (seems blurred); I cannot understand why.

I have cloned your github xaml sdk examples and pushed it on my personal account on this url https://gitlab.com/webartoli/repro-lowquality-exporttoimage-radtreeview. You can find a repro starting Diagram/Autoscrolling project.

Repro changes:

  • Positions button will move shapes on graph
  • Export image will export png file of current diagram in current directory with random filename.

attached a picture with a comparison of the same shape on a small diagram (300 x 150px) and on a big diagram (6100 x 6100 px).

 

Am I doing something wrong ?

How can I manage properly all the options, in order to obtain the same quality regardless of the size of the diagram ?

 

Thanks

Claudio

6 Answers, 1 is accepted

Sort by
0
Shawn
Top achievements
Rank 1
answered on 09 May 2016, 08:19 PM

You will have to create your own image exporter. The default one Telerik has implemented doesn't take the current zoom factor into account which is why your  large diagram images are blurred. Here is some code to point you in the right direction. The first block of code is a generic function which will take any FrameworkElement you pass into it and re-render the element to the provided Width/Height settings.

public void CopyImagetoFile(FrameworkElement surface, double width, double height, double dpi = 96d)
{
    var saveFile = new SaveFileDialog
    {
        InitialDirectory = Application.ExecutablePath,
        Filter = "PNG files (*.png)|*.png|All files (*.*)|*.*",
        FilterIndex = 1,
        RestoreDirectory = true
    };
 
    if (saveFile.ShowDialog() == System.Windows.Forms.DialogResult.OK)
    {
        var fullName = saveFile.FileName;
        var adjustedWidth = surface.ActualWidth * (dpi / 96d);
        var adjustedHeight = surface.ActualHeight * (dpi / 96d);
 
        // Now scale the layout to fit the bitmap
        var renderBitmap =
            new RenderTargetBitmap(
              (int)adjustedWidth,
                (int)adjustedHeight,
                dpi,
                dpi,
              PixelFormats.Default);
 
        var dv = new DrawingVisual();
 
        using (DrawingContext dc = dv.RenderOpen())
        {
            var vb = new BitmapCacheBrush(surface)
            {
                BitmapCache = new BitmapCache { SnapsToDevicePixels = true, EnableClearType = true }
            };
            dc.DrawRectangle(vb, null, new Rect(new Point(), new Size(width, height)));
        }
 
        renderBitmap.Render(dv);
        using (var outStream = new FileStream(fullName, FileMode.Create))
        {
            // Use png encoder for our data
            var encoder = new PngBitmapEncoder();
            // push the rendered bitmap to it
            encoder.Frames.Add(BitmapFrame.Create(renderBitmap));
            // save the data to the stream
            encoder.Save(outStream);
        }
    }
}

You can also change the DPI setting but be careful as your memory usage will grow significantly.

The call to this function looks like this:

var zoom = LocalDiagram.Zoom;
LocalDiagram.AutoFit();
var localBounds = LocalDiagram.CalculateEnclosingBounds();
 
var newWidth = localBounds.Width * zoom;
var newHeight = localBounds.Height * zoom;
             
CopyImagetoFile(LocalDiagram, newWidth, newHeight, 96d);

LocalDiagram is your RadDiagram. We multiple the bounds of the all shapes on the diagram by the current zoom factor to get the appropriate size you need to re-render for. You will want to zoom into your diagram to the desired size you want the output to look and then execute the above from a command/click event.

If your diagrams are going to be very large (i.e. 10000px+) you should really take a look at exporting to PDF instead of PNG as it will be much more efficient memory wise and look better. The UIElement export example is a great place to start: https://github.com/telerik/xaml-sdk/tree/master/PdfProcessing/ExportUIElement

Hope this helps,

Shawn

0
Petar Mladenov
Telerik team
answered on 10 May 2016, 07:30 AM
Hello Claudio and Shawn,

We would suggest increasing the dpi parameter in ExportToImage method, however, as Shawn said, the memory will be increased so please have this in mind.

Regards,
Petar Mladenov
Telerik
Do you need help with upgrading your AJAX, WPF or WinForms project? Check the Telerik API Analyzer and share your thoughts.
0
Claudio B.
Top achievements
Rank 1
answered on 10 May 2016, 08:04 AM

I have already tried multiple combination of DPI parameter in ExportToImage method but the resolution remains the same, labels unreadable, but generate images are bigger (according to this answer ExportToImage returnImageSize & dpi parameters).

I'll try Shawn's solution that seems consider the problem with a different approach that make sense. @Shawn: Thanks for your help.

Thanks,

Claudio

0
Claudio B.
Top achievements
Rank 1
answered on 10 May 2016, 03:52 PM
[quote]Claudio said:

I'll try Shawn's solution that seems consider the problem with a different approach.

[/quote]

Hello Petar and Shawn,

I have tired both of your solutions and i have updated the public repo based on telerik's examples.

Petar's

I have tried to encrease dpi value, encrease zoom level,  and to explicit image size with the same blurred resut.

This my last snippet about your solution:

public class ExportToImageTelerikCommand : ICommand
{
    public bool CanExecute(object parameter)
    {
        return true;
    }
 
    public void Execute(object parameter)
    {
        var radDiagram = parameter as RadDiagram;
        if (radDiagram == null) return;
 
        // Prepare image
        var encoder = new PngBitmapEncoder();
        var enclosingBounds = radDiagram.CalculateEnclosingBounds();
        var size = enclosingBounds.Size;
        var brush = Brushes.Transparent;
        var margin = default(Thickness);
        var dpi = 192D;
 
        // Save Image
        using (var stream = File.Open(GetTempFilename(), FileMode.Create))
        {
            radDiagram.ExportToImage(stream, encoder, enclosingBounds, size, brush, margin, dpi);
        }
    }
 
    public event EventHandler CanExecuteChanged;
 
    private static string GetTempFilename()
    {
        return $"{Path.GetFileNameWithoutExtension(Path.GetTempFileName())}.png";
    }
}

Shawn's

I've tried to apply your solution to my code, there is a little fine tuning to do about positions (i need to export the entire diagram at 100% zoom level) but the exported image is of the expected quality.

public class ExportToImageShawnCommand : ICommand
{
    public bool CanExecute(object parameter)
    {
        return true;
    }
 
    public void Execute(object parameter)
    {
        var radDiagram = parameter as RadDiagram;
 
        if (radDiagram == null) return;
 
        // Prepare image
        var enclosingBounds = radDiagram.CalculateEnclosingBounds();
        CopyImagetoFile(radDiagram, enclosingBounds, GetTempFilename());
    }
 
    public event EventHandler CanExecuteChanged;
 
    private static string GetTempFilename()
    {
        return $"{Path.GetFileNameWithoutExtension(Path.GetTempFileName())}.png";
    }
 
    private void CopyImagetoFile(FrameworkElement surface, Rect rectangle, string fullName, double dpi = 96d)
    {
        // Now scale the layout to fit the bitmap
        var renderBitmap = new RenderTargetBitmap((int) rectangle.Width, (int) rectangle.Height, dpi, dpi, PixelFormats.Default);
 
        var dv = new DrawingVisual();
 
        using (var dc = dv.RenderOpen())
        {
            var vb = new BitmapCacheBrush(surface)
            {
                BitmapCache = new BitmapCache
                {
                    SnapsToDevicePixels = true,
                    EnableClearType = true
                }
            };
            dc.DrawRectangle(vb, null, rectangle);
        }
 
        renderBitmap.Render(dv);
        using (var outStream = new FileStream(fullName, FileMode.Create))
        {
            // Use png encoder for our data
            var encoder = new PngBitmapEncoder();
            // push the rendered bitmap to it
            encoder.Frames.Add(BitmapFrame.Create(renderBitmap));
            // save the data to the stream
            encoder.Save(outStream);
        }
    }
}

In attachment you can find images, exported starting from example project.

 

With some fine tuning I'll can export images of great quality using Shawn's solution: but I obviously prefer to use Telerik's component features.

There is any chance to obtain a non blurred export using ExportToImage method (with any parameters value) ?

Is it possibile that I have hit a sort of bug ?

in a future version
0
Shawn
Top achievements
Rank 1
answered on 10 May 2016, 05:32 PM

Hi Claudio,

Glad to hear the solution will work for you.We spent a lot of time trying to figure out how to make this work. Trying to increase/change the input settings (bounds, DPI, etc) didn't have any noticeable improvement of image quality or would lead to OutOfMemoryExceptions due to DPI settings being too high.

In trying to find a solution, we took Telerik's implementation of the BitmpaUtils.CreateWriteableBitmap code as a base and worked on it until we discovered changing from a VisualBrush to a BitmapCacheBrush gave us the desired results. While the BitmapCacheBrush has a performance penalty, since it has to cache the image to video memory, the results are vastly improved over the VB.

I don't know how hard it would be for Telerik to change this on their end or what the ramifications would be for other code that uses this function but I would love to see this implemented as well. 

Shawn

0
Petar Mladenov
Telerik team
answered on 13 May 2016, 09:21 AM
Hello Shawn and Claudio,

Thank you for your suggestions. It's a matter of research, planning, impact and we currently cannot say whether the BitMapCacheBrush can be easily included in our Framework. However, we created a feature request and you can vote for it , this way increasing its development priority.

Regards,
Petar Mladenov
Telerik
Do you need help with upgrading your AJAX, WPF or WinForms project? Check the Telerik API Analyzer and share your thoughts.
Tags
Diagram
Asked by
Claudio B.
Top achievements
Rank 1
Answers by
Shawn
Top achievements
Rank 1
Petar Mladenov
Telerik team
Claudio B.
Top achievements
Rank 1
Share this question
or