1

Hello i am creating a website having an iFrame and a button.The function of that button is to get the screenshot of whatever that is displayed inside the iFrame and save it as an image on the harddisk.Below is the code i am using

private void saveURLToImage(string url) 
    { 
        if (!string.IsNullOrEmpty(url)) 
        { 
            string content = ""; 


            System.Net.WebRequest webRequest = WebRequest.Create(url); 
            System.Net.WebResponse webResponse = webRequest.GetResponse(); 
            System.IO.StreamReader sr = new StreamReader(webResponse.GetResponseStream(), System.Text.Encoding.GetEncoding("UTF-8"));

            content = sr.ReadToEnd(); 
            //save to file 
            byte[] b = Convert.FromBase64String(content); 
            System.IO.MemoryStream ms = new System.IO.MemoryStream(b); 
            System.Drawing.Image img = System.Drawing.Image.FromStream(ms); 
            img.Save(@"c:\pic.jpg", System.Drawing.Imaging.ImageFormat.Jpeg); 


            img.Dispose(); 
            ms.Close(); 
        } 
    } 

And here is the code for button click

 protected void Button1_Click(object sender, ImageClickEventArgs e)
{

saveURLToImage("http://www.google.com");

}

However when i click on the button i am getting an error

The input is not a valid Base-64 string as it contains a non-base 64 character, more than two padding characters, or a non-white space character among the padding characters.

on this line

byte[] b = Convert.FromBase64String(content);

i am unable to figure out how to resolve it.Any help would be greatly appreciated.Thank you

nitinvertigo
  • 1,180
  • 4
  • 32
  • 56

1 Answers1

5

In your case content is the raw HTML that makes the page up, not how it is rendered - that would be up to the browser to decide (have a look at it in the debugger) so, since that is not base 64 (which is a way to encode binary data using just ASCII characters), in order for this to work you would need to get the base 64 encoded binary data of a JPEG encoded image of however the browser has rendered the HTML, which you do not have.

I think this is not an easy thing to achieve in a web application since in the .net code you are running on the server and it is the job of the client to render the HTML into something that you can take a screenshot of. You could (and this will probably be really fragile so I would not really recommend it, hosting a winforms control like this in a web application is usually a recipe for trouble, but I think it may be possible) use a browser control on your server side and set the URL of that but then you would need to somehow screenshot it - this might help: Taking Website Screenshots With The WebBrowser Control.

Update

Tucked away in the comments of the web site I linked last is some code that actually works to take a screenshot of a web page (using a WebBrowser control). It requires that you have references to the following:

  • System.Drawing
  • System.Windows.Forms
  • Microsoft HTML Object Library (this is a COM reference, not a .NET one)

Here is a class that does the job we want (has just a single Render method on it that takes a Uri and a Size and returns a Bitmap):

using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
using mshtml;

public class HtmlToBitmapConverter
{
    public Bitmap Render(Uri uri, Size size)
    {
        var browser = new WebBrowser
                          {
                              ScrollBarsEnabled = false,
                              ScriptErrorsSuppressed = true,
                              Size = size
                          };
        browser.BringToFront();

        NavigateAndWaitForLoad(browser, uri, 0);

        var bitmap = new Bitmap(size.Width, size.Height);
        GetImage(browser.Document.DomDocument, bitmap, Color.White);
        return bitmap;
    }

    private void NavigateAndWaitForLoad(WebBrowser browser,
                                        Uri uri,
                                        int waitTime)
    {
        const int sleepTimeMiliseconds = 5000;

        browser.Navigate(uri);
        var count = 0;

        while (browser.ReadyState != WebBrowserReadyState.Complete)
        {
            Thread.Sleep(sleepTimeMiliseconds);
            Application.DoEvents();
            count++;

            if (count > waitTime / sleepTimeMiliseconds)
            {
                break;
            }
        }

        while (browser.Document.Body == null)
        {
            Application.DoEvents();
        }

        var document = (IHTMLDocument2)browser.Document.DomDocument;
        var style = (IHTMLStyle2)document.body.style;
        style.overflowX = "hidden";
        style.overflowY = "hidden";
    }

    private static void GetImage(object obj,
                                Image destination,
                                Color backgroundColor)
    {
        using (var graphics = Graphics.FromImage(destination))
        {
            var deviceContextHandle = IntPtr.Zero;
            var rectangle = new Rect
            {
                Right = destination.Width,
                Bottom = destination.Height
            };

            graphics.Clear(backgroundColor);
            try
            {
                deviceContextHandle = graphics.GetHdc();

                var viewObject = (IViewObject)obj;
                viewObject.Draw(1,
                                -1,
                                IntPtr.Zero,
                                IntPtr.Zero,
                                IntPtr.Zero,
                                deviceContextHandle,
                                ref rectangle,
                                IntPtr.Zero,
                                IntPtr.Zero,
                                0);
            }
            finally
            {
                if (deviceContextHandle != IntPtr.Zero)
                {
                    graphics.ReleaseHdc(deviceContextHandle);
                }
            }
        }
    }

    [ComImport]
    [Guid("0000010D-0000-0000-C000-000000000046")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    private interface IViewObject
    {
        void Draw([MarshalAs(UnmanagedType.U4)] uint dwAspect,
                  int lindex,
                  IntPtr pvAspect,
                  [In] IntPtr ptd,
                  IntPtr hdcTargetDev,
                  IntPtr hdcDraw,
                  [MarshalAs(UnmanagedType.Struct)] ref Rect lprcBounds,
                  [In] IntPtr lprcWBounds,
                  IntPtr pfnContinue,
                  [MarshalAs(UnmanagedType.U4)] uint dwContinue);
    }

    [StructLayout(LayoutKind.Sequential, Pack = 4)]
    public struct Rect
    {
        public int Left;

        public int Top;

        public int Right;

        public int Bottom;
    }
}

NOTE As I said before, I am not sure this is a great idea to be used in a web application for a couple of reasons:

  1. It is a Windows Forms control so the way it deals with memory may not be compatible with being used in a web application.
  2. It means that the account taking the screenshot will be the one that the web application is running as, not necessarily the end user.

OK, so I think the above would be fine in a winforms app but maybe not appropriate for the web, but, hey, we can make it work anyway, here goes...

I am assuming you are going for a regular ASP .NET web application in which case you would have something like this in the .aspx page:

<asp:Button runat="server" OnClick="TakeScreenShot" Text="Take Screenshot"/>

Then in the code behind the TakeScreenshot method would look like this:

protected void TakeScreenShot(object sender, EventArgs e)
{
    Uri uri = new Uri("http://www.google.com");

    // Because it is a WebBrowser control it needs to run in an STA 
    // thread - what we will do is render the image to a Bitmap then
    // store the raw bytes in this byte array from a newly created
    // thread
    byte[] screenshot = null;
    var t = new Thread(() =>
                           {
                               using (var ms = new MemoryStream())
                               {
                                   // The screenshot object contains a 640x480 
                                   // screenshot 
                                   var bitmap = new HtmlToBitmapConverter()
                                       .Render(uri,
                                               new Size(640, 480));
                                   bitmap.Save(ms, ImageFormat.Jpeg);
                                   screenshot = ms.ToArray();
                               }
                           });
    t.SetApartmentState(ApartmentState.STA);
    t.Start();
    t.Join();            

    // Here we have the JPEG encoded bytes of the image - we can
    // just save them to a file like so...
    using (var f = File.Create(@"c:\google.jpg"))
    {
        f.Write(screenshot, 0, screenshot.Length);
    }
}

There you go - c:\google.jpg will have a screenshot of Google in it.

kmp
  • 10,535
  • 11
  • 75
  • 125
  • Hello thanx for responding.My iframe contains reports from pentaho.Also i have used the method you specified before only but all i get is a blank photo.I have tried many screenshot techniques but they don't work.I just want to take the screenshot of the contents in my iframe. – nitinvertigo Apr 26 '12 at 05:06
  • @nitinvertigo An actual working solution is in the comments of that article (I am not sure the code in the article actually works - seems there are lots of things on the web about how to do this that do not work). I have simplified it and updated this answer with it. I also show how to call it from an asp .net webforms application – kmp Apr 26 '12 at 07:03
  • Thank you very much..Your code works perfectly :) I owe u a treat :) – nitinvertigo Apr 27 '12 at 07:21
  • No problem :) (by the way, if it answered your question you can just tick the accept button) – kmp Apr 27 '12 at 07:46