15

I have a Image control inside my wpf application, which has a large image inside of it, but the control itself is only 60x150, this means it only shows a certain portion of this image. What is the easiest way of saving the visible portion to a file?

Thank you for your help.

[EDIT] I ended up using code found here (which I've not been able to locate before posting here)...

Grid r = new Grid();
        r.Background = new ImageBrush(image2.Source);


    System.Windows.Size sz = new System.Windows.Size(image2.Source.Width, image2.Source.Height);
    r.Measure(sz);
    r.Arrange(new Rect(sz));

    RenderTargetBitmap rtb = new RenderTargetBitmap((int)image2.Source.Width, (int)image2.Source.Height, 96d, 96d, PixelFormats.Default);
    rtb.Render(r);

    BmpBitmapEncoder encoder = new BmpBitmapEncoder();
    encoder.Frames.Add(BitmapFrame.Create(rtb));

    FileStream fs = File.Open(@"C:\lol.png", FileMode.Create);
    encoder.Save(fs);
    fs.Close();
Meh
  • 607
  • 1
  • 9
  • 19

2 Answers2

20

You could use RenderTargetBitmap class and BitmapEncoder.

Define these methods:

void SaveToBmp(FrameworkElement visual, string fileName)
{
    var encoder = new BmpBitmapEncoder();
    SaveUsingEncoder(visual, fileName, encoder);
}

void SaveToPng(FrameworkElement visual, string fileName)
{
    var encoder = new PngBitmapEncoder();
    SaveUsingEncoder(visual, fileName, encoder);
}

// and so on for other encoders (if you want)


void SaveUsingEncoder(FrameworkElement visual, string fileName, BitmapEncoder encoder)
{
    RenderTargetBitmap bitmap = new RenderTargetBitmap((int)visual.ActualWidth, (int)visual.ActualHeight, 96, 96, PixelFormats.Pbgra32);
    bitmap.Render(visual);
    BitmapFrame frame = BitmapFrame.Create(bitmap);
    encoder.Frames.Add(frame);

    using (var stream = File.Create(fileName))
    {
        encoder.Save(stream);
    }
}

If you have your Image control inside a container like this:

<Grid x:Name="MyGrid">
    <Image Name="MyImage" Stretch="None"></Image>
</Grid>

You just need to do so:

SaveToPng(MyGrid, "image.png");

Otherwise you can simply pass the dimensions you want when you use RenderTargetBitmap:

SaveToPng(MyImage, "image.png");

...

RenderTargetBitmap bitmap = new RenderTargetBitmap(YourWidth, YourHeight, 96, 96, PixelFormats.Pbgra32);
gliderkite
  • 8,828
  • 6
  • 44
  • 80
  • 5
    Thanks for this, it would be fantastic if not for one small issue.. The saved image is pitch black... Any ideas how that happened or how to fix it? – Meh May 06 '12 at 15:22
  • I've done a test project in VB.NET: [here it is a sample](http://ideone.com/wZH72) if it can help; and [here](http://ideone.com/CJrJA) the XAML. – gliderkite May 06 '12 at 15:45
  • @gilderkite I am assuming by saying "MyImage" you mean the image control within WPF, then yes I have tried this – Meh May 06 '12 at 16:13
  • OK this is not working despite your code not giving errors. I just get a black image as a result. My image is - system.windows.controls.image – Meh May 06 '12 at 16:33
  • 2
    @Capt.Morgan, I know this is old but I ran into the same problem and you need to place your rendered control on a parent control with no margins as explained here: http://weblog.west-wind.com/posts/2007/Sep/10/Rendering-a-WPF-Container-to-Bitmap – BryanJ Dec 03 '13 at 18:59
  • Is it possible to save a Control that is created dynamically and is not displayed in the UI? Because when I do that I get a black image – OrLevi Jan 17 '23 at 12:24
3

I ran into the same 'black' image issue that other did when using gliderkite's solution. The 'black' image appears to be due to the FrameworkElement's margin causing it to get rendered outside of the captured image. I found the workaround in a comment on Rick Stahl's blog

Specifically, measuring and arranging prior to rendering gives it a chance to adjust itself to the fact there are no margins in the picture. The following is a static class I now re-use for screen captures. This is based of of gliderkite's answer and the information on Rick Stahl's blog.

public static class ScreenCapture
{
    public static void SaveToBmp(FrameworkElement visual, string fileName)
    {
        var encoder = new BmpBitmapEncoder();
        SaveUsingEncoder(visual, fileName, encoder);
    }

    public static void SaveToPng(FrameworkElement visual, string fileName)
    {
        var encoder = new PngBitmapEncoder();
        SaveUsingEncoder(visual, fileName, encoder);
    }

    public static void SaveToJpeg(FrameworkElement visual, string fileName)
    {
        var encoder = new JpegBitmapEncoder();
        SaveUsingEncoder(visual, fileName, encoder);
    }

    private static void SaveUsingEncoder(FrameworkElement visual, string fileName, BitmapEncoder encoder)
    {
        RenderTargetBitmap bitmap = new RenderTargetBitmap((int)visual.ActualWidth, (int)visual.ActualHeight, 96, 96, PixelFormats.Pbgra32);
        Size visualSize = new Size(visual.ActualWidth, visual.ActualHeight);
        visual.Measure(visualSize);
        visual.Arrange(new Rect(visualSize));
        bitmap.Render(visual);
        BitmapFrame frame = BitmapFrame.Create(bitmap);
        encoder.Frames.Add(frame);

        using (var stream = File.Create(fileName))
        {
            encoder.Save(stream);
        }
    }
}
denver
  • 2,863
  • 2
  • 31
  • 45
  • I tried other methods, but my width and height on the rendered image were always too small, meaning I had content cutting off on the bottom and right of the image. With this method, though, I get the exact dimensions that I'm expecting. Dunno why... – Kevin Lee Garner Jan 14 '19 at 18:14