low resolution using ExportToImage on big diagram

7 posts, 0 answers
  1. Claudio B.
    Claudio B. avatar
    4 posts
    Member since:
    Jan 2015

    Posted 08 May Link to this post

    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

  2. Shawn
    Shawn avatar
    39 posts
    Member since:
    Jul 2012

    Posted 09 May Link to this post

    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

  3. UI for WPF is Visual Studio 2017 Ready
  4. Petar Mladenov
    Admin
    Petar Mladenov avatar
    2891 posts

    Posted 10 May Link to this post

    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.
  5. Claudio B.
    Claudio B. avatar
    4 posts
    Member since:
    Jan 2015

    Posted 10 May in reply to Petar Mladenov Link to this post

    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

  6. Claudio B.
    Claudio B. avatar
    4 posts
    Member since:
    Jan 2015

    Posted 10 May in reply to Claudio B. Link to this post

    Claudio said:

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

    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
  7. Shawn
    Shawn avatar
    39 posts
    Member since:
    Jul 2012

    Posted 10 May in reply to Claudio B. Link to this post

    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

  8. Petar Mladenov
    Admin
    Petar Mladenov avatar
    2891 posts

    Posted 13 May Link to this post

    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.
Back to Top
UI for WPF is Visual Studio 2017 Ready