9

I want to save my canvas to bitmap. I found some examples in internet, but all of those saves only black image (with size of my canvas). What can I do with this?

Code:

    public static void SaveCanvasToFile(Canvas surface, string filename)
    {
        Size size = new Size(surface.Width, surface.Height);

        surface.Measure(size);
        surface.Arrange(new Rect(size));

        // Create a render bitmap and push the surface to it
        RenderTargetBitmap renderBitmap =
          new RenderTargetBitmap(
            (int)size.Width,
            (int)size.Height,
            96d,
            96d,
            PixelFormats.Pbgra32);
        renderBitmap.Render(surface);

        // Create a file stream for saving image
        using (FileStream outStream = new FileStream(filename, FileMode.Create))
        {               
            BmpBitmapEncoder encoder = new BmpBitmapEncoder();
            // push the rendered bitmap to it
            encoder.Frames.Add(BitmapFrame.Create(renderBitmap));
            // save the data to the stream
            encoder.Save(outStream);
        }}
H.B.
  • 166,899
  • 29
  • 327
  • 400
zc21
  • 91
  • 1
  • 1
  • 2
  • Did you step through this with the debugger? Is there anything suspicious, like `surface.Width` returning 0? – H.B. May 01 '11 at 20:29
  • No, in the debugger, everything looks normal – zc21 May 01 '11 at 20:42
  • Using your code i just successfully rendered a whole bunch (~25) of controls (i used the `ActualWidth` & `ActualHeight` properties though, because i rarely set sizes explicitly) – H.B. May 01 '11 at 21:16
  • Did you ever get a solution to this? I'm having basically the same problem... – JToland Jun 01 '11 at 17:27

7 Answers7

12

Try this answer:

public void ExportToPng(Uri path, Canvas surface)
{
  if (path == null) return;

  // Save current canvas transform
  Transform transform = surface.LayoutTransform;
  // reset current transform (in case it is scaled or rotated)
  surface.LayoutTransform = null;

  // Get the size of canvas
  Size size = new Size(surface.Width, surface.Height);
  // Measure and arrange the surface
  // VERY IMPORTANT
  surface.Measure(size);
  surface.Arrange(new Rect(size));

  // Create a render bitmap and push the surface to it
  RenderTargetBitmap renderBitmap = 
    new RenderTargetBitmap(
      (int)size.Width, 
      (int)size.Height, 
      96d, 
      96d, 
      PixelFormats.Pbgra32);
  renderBitmap.Render(surface);

  // Create a file stream for saving image
  using (FileStream outStream = new FileStream(path.LocalPath, FileMode.Create))
  {
    // Use png encoder for our data
    PngBitmapEncoder encoder = new PngBitmapEncoder();
    // push the rendered bitmap to it
    encoder.Frames.Add(BitmapFrame.Create(renderBitmap));
    // save the data to the stream
    encoder.Save(outStream);
  }

  // Restore previously saved layout
  surface.LayoutTransform = transform;
}

This answer was copied here for convenience from [this page.]<====LINK DEAD(http://denisvuyka.wordpress.com/2007/12/03/wpf-diagramming-saving-you-canvas-to-image-xps-document-or-raw-xaml/)

SOUPaLOOP
  • 121
  • 7
Icemanind
  • 47,519
  • 50
  • 171
  • 296
  • 2
    This works fine for me.. Actually I creates hundreds of image using this code but once in while some of the images this code creates are black. what could be issue? – SST Oct 25 '12 at 12:05
  • 1
    This works for me too, and it also saves the child controls of the canvas, for examples Buttons. Many other solutions found on internet will only save the canvas without the child controls. – Ben Feb 15 '23 at 08:35
0
var fileName = "img.jpg";
var bitMap = new WriteableBitmap(DrawCanvas, null);
var ms = new MemoryStream();
System.Windows.Media.Imaging.Extensions.SaveJpeg(bitMap, ms, bitMap.PixelWidth, bitMap.PixelHeight, 0, 100);
ms.Seek(0, SeekOrigin.Begin);
var library = new MediaLibrary();
library.SavePicture(string.Format("{0}", fileName), ms);
reza.cse08
  • 5,938
  • 48
  • 39
0

PAY ATTENTION

if your render is a black image it is because of your incorrect sizing.

this is a good example for you:

RenderTargetBitmap rtb = new RenderTargetBitmap(width, height, mXdpi, mYdpi, System.Windows.Media.PixelFormats.Default);
        rtb.Render(my_canvas);

        BitmapEncoder pngEncoder = new PngBitmapEncoder();
        pngEncoder.Frames.Add(BitmapFrame.Create(rtb));

        using (var fs = System.IO.File.OpenWrite("test.png"))
        {
            pngEncoder.Save(fs);
        }

this code saves a png image from your bitmap that rendered from your canvas.

Hope helps you.

0

If you are working with windows 10 UWP apps, check this page:

RenderTargetBitmap Class on Microsoft Docs

And as a bonus UWP have simplified the process.

Maciej S.
  • 752
  • 10
  • 22
  • A link to a solution is welcome, but please ensure your answer is useful without it: [add context around the link](//meta.stackexchange.com/a/8259) so your fellow users will have some idea what it is and why it’s there, then quote the most relevant part of the page you're linking to in case the target page is unavailable. [Answers that are little more than a link may be deleted.](//stackoverflow.com/help/deleted-answers) – Jonathan Mar 15 '19 at 23:18
0

In my render-code i call target.UpdateLayout(); after target.Arrange(new Rect(size));, maybe that will fix it. Also note that if the canvas background is not set it will be rendered as transparent, while encoding to BMP that might turn into solid black, so if you only have black objects they might be invisible.

H.B.
  • 166,899
  • 29
  • 327
  • 400
  • If I encoding to PNG I get transparent image, but canvas background is set to White – zc21 May 01 '11 at 21:02
  • Are there any transforms applied on the Canvas? – H.B. May 01 '11 at 21:03
  • I think there is just something wrong with your canvas (see my comment to the question) – H.B. May 01 '11 at 21:18
  • Really, I tried this code on other canvas (with Width, Height = Auto) and it's work correctly. But i don't understand why the problem canvas can't be rendered. – zc21 May 01 '11 at 21:34
  • Btw, how I can get ActualW and ActualH of the control with Auto Width and Height if it had never drawn (placed in other Tab in TabControl) – zc21 May 01 '11 at 21:36
  • Must have to do with the layout system, but i cannot tell you why and how that works they way it does. Also no clue about your second comment, i always did rendering of controls via trial and error, and if it works i don't touch it anymore. – H.B. May 01 '11 at 21:40
  • If I move the canvas to top left corner of window, then this code work correctly. It makes a capture of top left path of window with size of target canvas. Why?) Thanks you – zc21 May 02 '11 at 07:51
  • Beats me, rendering canvases is one of the most unintuitive things ever. Does that only happen if the height and width is set explicity or even when on auto> – H.B. May 02 '11 at 07:57
  • As i said i do not know why that is the case, it's fortunate that you found out that it happens, now you can at least react to it. – H.B. May 02 '11 at 08:52
0
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Effects;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
public static class RenderVisualService
{
    public const double defaultDpi = 96.0;
    public static void SaveAsPng(this FrameworkElement frElement, string fullFilePath, FormattedText title = null, Point pt = default(Point))
    {
        BitmapImage img = RenderVisualService.RenderToPNGImageSource(frElement, title, pt) as BitmapImage;
        img.SavePng(fullFilePath);
    }
    public static ImageSource RenderToPNGImageSource(Visual targetControl, FormattedText Title = null, Point pt = default(Point))
    {
        var renderTargetBitmap = targetControl.GetRenderTargetBitmapFromControl(Title);
        var encoder = new System.Windows.Media.Imaging.PngBitmapEncoder();
        encoder.Frames.Add(System.Windows.Media.Imaging.BitmapFrame.Create(renderTargetBitmap));
        var result = new System.Windows.Media.Imaging.BitmapImage();
        using (var memoryStream = new System.IO.MemoryStream())
        {
            encoder.Save(memoryStream);
            memoryStream.Seek(0, System.IO.SeekOrigin.Begin);

            result.BeginInit();
            result.CacheOption = System.Windows.Media.Imaging.BitmapCacheOption.OnLoad;
            result.StreamSource = memoryStream;
            result.EndInit();
        }
        return result;
    }
    public static RenderTargetBitmap ExportToPng(this Shape shape, Uri path)
    {
        // Save current canvas transform
        Transform transform = shape.LayoutTransform;
        // reset current transform (in case it is scaled or rotated)
        shape.LayoutTransform = null;

        // Get the size of canvas
        Size size;
        //if (!sizeOfShape.HasValue) size = new Size(shape.Width, shape.Height);
        //else size = sizeOfShape.Value;
        // Measure and arrange the surface
        // VERY IMPORTANT
        shape.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
        size = shape.DesiredSize;
        shape.Arrange(new Rect(size));
        // Create a render bitmap and push the surface to it
        RenderTargetBitmap renderBitmap = new RenderTargetBitmap((int)size.Width, (int)size.Height, 96d, 96d, PixelFormats.Pbgra32);
        renderBitmap.Render(shape);
        if (path == null) return renderBitmap;
        // Create a file stream for saving image
        using (FileStream outStream = new FileStream(path.LocalPath, FileMode.Create))
        {
            // Use png encoder for our data
            PngBitmapEncoder encoder = new PngBitmapEncoder();
            // push the rendered bitmap to it
            encoder.Frames.Add(BitmapFrame.Create(renderBitmap));
            // save the data to the stream
            encoder.Save(outStream);
        }

        // Restore previously saved layout
        shape.LayoutTransform = transform;
        return renderBitmap;
    }
    public static RenderTargetBitmap ExportToPng(this Canvas surface, String path, Size? sizeOfCanvas = null)
    {
        // Save current canvas transform
        Transform transform = surface.LayoutTransform;
        // reset current transform (in case it is scaled or rotated)
        var OldTransform = surface.LayoutTransform;
        surface.LayoutTransform = null;
        try
        {
            // Get the size of canvas
            Size size;
            if (!sizeOfCanvas.HasValue) size = new Size(surface.Width, surface.Height);
            else size = sizeOfCanvas.Value;
            //Size sizeR = new Size(300, 300);
            // Measure and arrange the surface
            // VERY IMPORTANT
            surface.Measure(size);
            surface.Arrange(new Rect(size));

            // Create a render bitmap and push the surface to it
            RenderTargetBitmap renderBitmap = new RenderTargetBitmap((int)size.Width, (int)size.Height, 96d, 96d, PixelFormats.Pbgra32);
            renderBitmap.Render(surface);
            if (path == null) return renderBitmap;
            // Create a file stream for saving image
            using (FileStream outStream = new FileStream(path, FileMode.Create))
            {
                // Use png encoder for our data
                PngBitmapEncoder encoder = new PngBitmapEncoder();
                // push the rendered bitmap to it
                encoder.Frames.Add(BitmapFrame.Create(renderBitmap));
                // save the data to the stream
                encoder.Save(outStream);
            }
            // Restore previously saved layout
            surface.LayoutTransform = transform;
            return renderBitmap;
        }
        catch { return null; }
        finally { surface.LayoutTransform = OldTransform; }
    }
    public static void SaveBitmapSourceToPNGFile(System.Windows.Media.Imaging.BitmapSource renderTargetBitmap, string filename)
    {
        var encoder = new System.Windows.Media.Imaging.PngBitmapEncoder();
        encoder.Frames.Add(System.Windows.Media.Imaging.BitmapFrame.Create(renderTargetBitmap));
        var result = new System.Windows.Media.Imaging.BitmapImage();
        try { using (var fileStream = new System.IO.FileStream(filename, System.IO.FileMode.Create)) { encoder.Save(fileStream); } }
        catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"There was an error saving the file: {ex.ProcessException()}"); }
    }
    public static System.Windows.Media.Imaging.BitmapSource GetRenderTargetBitmapFromControl(this Visual targetControl, FormattedText Title, Point ptTitlePosition = default(Point), double dpi = defaultDpi)
    {
        if (targetControl == null) return null;
        var bounds = VisualTreeHelper.GetDescendantBounds(targetControl);
        //int pixelX = (int)bounds.Width; int pixelY = (int)bounds.Height;
        //IntPtr hDc = Helper.GetDC(IntPtr.Zero);
        //if (hDc != IntPtr.Zero)
        //{
        //    int dpiX = Helper.GetDeviceCaps(hDc, Helper.LOGPIXELSX);
        //    int dpiY = Helper.GetDeviceCaps(hDc, Helper.LOGPIXELSY);
        //    Helper.ReleaseDC(IntPtr.Zero, hDc);
        //    pixelX = (int)(((double)dpiX / 96) * (double)bounds.Width);
        //    pixelY = (int)(((double)dpiY / 96) * (double)bounds.Height);
        //}
        var renderTargetBitmap = new System.Windows.Media.Imaging.RenderTargetBitmap(/*(int)(bounds.Width * dpi / 96.0),(int)(bounds.Height * dpi / 96.0),*/(int)bounds.Size.Width, (int)bounds.Size.Height, dpi, dpi, PixelFormats.Pbgra32);
        var drawingVisual = new DrawingVisual();
        using (DrawingContext dc = drawingVisual.RenderOpen())
        {
            var visualBrush = new VisualBrush(targetControl);
            dc.DrawRectangle(visualBrush, null, new Rect(new Point(), bounds.Size));
            if (null != Title) dc.DrawText(Title, ptTitlePosition);
        }
        renderTargetBitmap.Render(drawingVisual);
        //Clipboard.SetImage(renderTargetBitmap);
        return renderTargetBitmap;
    }
    public static Size GetDimensionsFromWindowToPixel(this ScrollViewer scroller)
    {
        IntPtr hDc = Helper.GetDC(IntPtr.Zero);
        double pixelX = -10, pixelY = -10;
        if (hDc != IntPtr.Zero)
        {
            int dpiX = Helper.GetDeviceCaps(hDc, Helper.LOGPIXELSX);
            int dpiY = Helper.GetDeviceCaps(hDc, Helper.LOGPIXELSY);

            Helper.ReleaseDC(IntPtr.Zero, hDc);

            pixelX = (int)(((double)dpiX / 96) * (double)(scroller.ActualWidth - SystemParameters.VerticalScrollBarWidth));
            pixelY = (int)(((double)dpiY / 96) * (double)(scroller.ActualHeight - SystemParameters.HorizontalScrollBarHeight));
        }
        Size sz = new Size(0, 0);
        if (pixelX > 0) sz.Width = pixelX; if (pixelY > 0) sz.Height = pixelY;
        return sz;
    }

    /// <summary>
    /// This method is almost same as RenderVisualService.SaveAsPng , this method explores the range of possibilites in one place
    /// which is also an extension Method , this one has some cool arguments</summary>
    /// <param name="fr"></param>
    /// <param name="pngIsTrueAndClipBoardisFalse"></param> <param name="pngFileFullPath"></param>
    /// <param name="x"></param> <param name="y"></param>
    /// <param name="wd"></param> <param name="ht"></param>
    /// <param name="margin"></param>
    /// <param name="dotsPerInch"></param>
    public static void DumpFrameworkElementToPng(this FrameworkElement fr, bool pngIsTrueAndClipBoardisFalse = true, String pngFileFullPath = @"C:\WOI\A\control.png", int x = 0, int y = 0, int wd = 0, int ht = 0, int margin = 20, int border = 5, double dotsPerInch = 96.0)
    {
        Directory.CreateDirectory(System.IO.Path.GetDirectoryName(pngFileFullPath));
        System.Windows.Media.PixelFormat pf = System.Windows.Media.PixelFormats.Pbgra32;
        GeneralTransform t = fr.TransformToVisual(fr.Parent as Visual);

        Vector topLeft = (Vector)t.Transform(new Point(0, 0));
        Vector topRight = (Vector)t.Transform(new Point(fr.ActualWidth, 0));
        Vector botmLeft = (Vector)t.Transform(new Point(0, fr.ActualHeight));

        double renderedWidth = (topRight - topLeft).Length; double widthScale = renderedWidth / fr.ActualWidth;
        double renderedHight = (botmLeft - topLeft).Length; double heightScale = renderedHight / fr.ActualHeight;

        //int fullWidth = (int)fr.ActualWidth + 2 * margin; int fullHeight = (int)fr.ActualHeight + 2 * margin;
        int fullWidth = (int)renderedWidth + (2 * margin); int fullHeight = (int)renderedHight + (2 * margin);
        var renderTargetBitmap = new RenderTargetBitmap(fullWidth, fullHeight, dotsPerInch, dotsPerInch, pf);

        var drawingVisual = new System.Windows.Media.DrawingVisual();
        RenderOptions.SetBitmapScalingMode(drawingVisual, BitmapScalingMode.HighQuality);
        using (System.Windows.Media.DrawingContext dc = drawingVisual.RenderOpen())
        {
            var vb = new System.Windows.Media.VisualBrush(fr);
            dc.DrawRectangle(Brushes.AliceBlue, null, new Rect(0, 0, fullWidth, fullHeight));
            dc.DrawRectangle(vb, null, new Rect(margin, margin, fullWidth, fullHeight));
        }
        renderTargetBitmap.Render(drawingVisual);
        BitmapSource bmpSource = renderTargetBitmap;
        if (wd != 0 && ht != 0)
        {
            if (wd > fr.ActualWidth || ht > fr.ActualHeight)
            {
                wd = fullWidth; ht = fullHeight;
                x = y = 0;
            }
            else { wd -= margin; ht -= margin; }
            bmpSource = new CroppedBitmap(renderTargetBitmap, new Int32Rect(x + margin, y + margin, wd, ht));
            if (margin != 0)
            {
                var dv = new DrawingVisual();
                using (var dc = dv.RenderOpen())
                {
                    fullWidth = wd + 2 * margin; fullHeight = ht + 2 * margin;
                    dc.DrawRectangle(Brushes.AliceBlue, null, new Rect(0, 0, fullWidth, fullHeight));
                    dc.DrawImage(bmpSource, new Rect(margin, margin, wd, ht));
                    if (border != 0) dc.DrawRectangle(null, new Pen(Brushes.DarkBlue, border), new Rect(margin - border / 2, margin - border / 2, wd + border, ht + border));
                }
                renderTargetBitmap = new RenderTargetBitmap(fullWidth, fullHeight, dotsPerInch, dotsPerInch, pf);
                renderTargetBitmap.Render(dv);
                bmpSource = new CroppedBitmap(renderTargetBitmap, new Int32Rect(0, 0, fullWidth, fullHeight));
            }
        }
        if (pngIsTrueAndClipBoardisFalse)
        {
            bmpSource.SavePng(pngFileFullPath);
            Helper.ShellExecute(IntPtr.Zero, "open", pngFileFullPath, null, null, 1);
        }
        else
        {
            Clipboard.SetImage(bmpSource);
            Helper.ShellExecute(IntPtr.Zero, "open", "pBrush", null, null, 1);
        }
    }
    /// <summary>
    /// Applicable to methods like MakeJulia_Sai in SpecialCanvas
    /// Usage : RenderVisualService.SaveARenderMethodToPng(Kanva.MakeJulia_Sai, Kanva.ActualWidth / 4, Kanva.ActualHeight / 4);
    /// </summary>
    /// <param name="method"></param>
    public static void SaveARenderMethodToPngOrClipBoard(Action<System.Windows.Media.DrawingContext> method, double wd, double ht, bool pngIsTrueAndClipBoardisFalse = true, String pngFileFullPath = @"C:\WOI\A\Bindu.png")
    {
        double dotsPerInch = 96.0; System.Windows.Media.PixelFormat pf = System.Windows.Media.PixelFormats.Pbgra32;
        //double wd = Kanva.ActualWidth / 4; double ht = Kanva.ActualHeight / 4;
        var renderTargetBitmap = new System.Windows.Media.Imaging.RenderTargetBitmap((int)wd, (int)ht, dotsPerInch, RenderVisualService.defaultDpi, pf);
        var drawingVisual = new System.Windows.Media.DrawingVisual();
        using (System.Windows.Media.DrawingContext dc = drawingVisual.RenderOpen()) method(dc);
        renderTargetBitmap.Render(drawingVisual);
        if (pngIsTrueAndClipBoardisFalse)
        {
            Directory.CreateDirectory(System.IO.Path.GetDirectoryName(pngFileFullPath));
            renderTargetBitmap.SavePng(pngFileFullPath);
            Helper.ShellExecute(IntPtr.Zero, "open", pngFileFullPath, null, null, 1);
        }
        else Clipboard.SetImage(renderTargetBitmap);
    }
}

My favorite is

Kanva.DumpFrameworkElementToPng(pngIsTrueAndClipBoardisFalse: false)

Where Kanva is some Canvas with some controls , images , etc in it. After the call to DumpFrameworkElementToPng(...) the image is saved in ClipBoard

Dr.Sai
  • 303
  • 4
  • 5
-1

try set the canvas background colour to white