7

I'm using Selenium & Google Chrome Driver to open pages programatically. On each page there is a dynamically generated image which I'd like to download. At the moment, I'm waiting for the page to finish loading, then I grab the image URL and download it using System.Net.WebClient.

That works fine except I'm downloading the images twice - once in the browser, once with WebClient. The problem is that each image is roughly 15MB and downloading twice adds up quickly.

So - is it possible to grab the image straight from Google Chrome?

Fidel
  • 7,027
  • 11
  • 57
  • 81

10 Answers10

25

One way is to get base64 string of the image with javascript that is executed by webdriver. Then you can save base64string of the image to file.

Basically, if your image is

<img id='Img1' src='someurl'>

then you can convert it like

var base64string = driver.ExecuteScript(@"
    var c = document.createElement('canvas');
    var ctx = c.getContext('2d');
    var img = document.getElementById('Img1');
    c.height=img.naturalHeight;
    c.width=img.naturalWidth;
    ctx.drawImage(img, 0, 0,img.naturalWidth, img.naturalHeight);
    var base64String = c.toDataURL();
    return base64String;
    ") as string;

var base64 = base64string.Split(',').Last();
using (var stream = new MemoryStream(Convert.FromBase64String(base64)))
{
    using (var bitmap = new Bitmap(stream))
    {
        var filepath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ImageName.png");
        bitmap.Save(filepath, ImageFormat.Png);
    }
}
mehmet mecek
  • 2,615
  • 2
  • 21
  • 25
9

Yes, you do this in several steps:

  1. Take a screenshot of the webpage and save it to disk
  2. Find the image element
  3. Find the image element location, width and height
  4. Crop the image you need from the screenshot you took in step 1
  5. Save the image to disk (or do something else with it)

Sample code - please add your code to catch exceptions

        IWebDriver driver = new ChromeDriver();

        //replace with the page you want to navigate to
        string your_page = "https://www.google.com"; 
        driver.Navigate().GoToUrl(your_page);

        ITakesScreenshot ssdriver = driver as ITakesScreenshot;
        Screenshot screenshot = ssdriver.GetScreenshot();

        Screenshot tempImage = screenshot;

        tempImage.SaveAsFile(@"C:\full.png", ImageFormat.Png);

        //replace with the XPath of the image element
        IWebElement my_image = driver.FindElement(By.XPath("//*[@id=\"hplogo\"]/canvas[1]"));

        Point point = my_image.Location;
        int width = my_image.Size.Width;
        int height = my_image.Size.Height;

        Rectangle section = new Rectangle(point, new Size(width, height));
        Bitmap source = new Bitmap(@"C:\full.png");
        Bitmap final_image = CropImage(source, section);

        final_image.Save(@"C:\image.jpg");

the CropImage method was posted by James Hill, How to cut a part of image in C#

but I will add it here as well for clarity

    public Bitmap CropImage(Bitmap source, Rectangle section)
    {
        Bitmap bmp = new Bitmap(section.Width, section.Height);
        Graphics g = Graphics.FromImage(bmp);
        g.DrawImage(source, 0, 0, section, GraphicsUnit.Pixel);
        return bmp;
    }
Community
  • 1
  • 1
TH Todorov
  • 1,129
  • 11
  • 26
  • 1
    The drawback is that this saves the image as rendered, so an image that has been resized or cut off won't save properly. Since the question says the image is 15MB that's especially likely to be in play – gss Sep 12 '18 at 20:59
  • 1
    This way only works with the images without any css-rules like margin or padding which offsets it's position. – Max Bender Dec 06 '18 at 11:08
3

All the above answers work. However, they all have limitations. mecek's method is cool, but it only works on browsers that support html 5 (although most browsers now do), and it will downgrade the image quality. The screenshot method will also downgrade image quality. Using System.Net.WebClient can avoid this issue, but won't work in the case of downloading a captcha image. Actually the only way that works for me when downloading a captcha image is using the Actions class (or Robot if you are using Selenium's java version), something like below:

using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Interactions;
using System.Windows.Automation;//you need to add UIAutomationTypes and UIAutomationClient to references
using System.Runtime.InteropServices;

[DllImport("User32.dll")]
static extern int SetForegroundWindow(IntPtr point);

private IntPtr getIntPtrHandle(IWebDriver driver, int timeoutSeconds = 30)
{
        var end = DateTime.Now.AddSeconds(timeoutSeconds);
        while (DateTime.Now < end)
        {
            var ele = AutomationElement.RootElement;
            foreach (AutomationElement child in ele.FindAll(TreeScope.Children, Condition.TrueCondition))
            {
                if (!child.Current.Name.Contains(driver.Title)) continue;
                return new IntPtr(child.Current.NativeWindowHandle);
            }
        }
        return IntPtr.Zero;
}

private void downloadCaptcha(IWebDriver chromeDriver)
{
    OpenQA.Selenium.IWebElement captchaImage = chromeDriver.FindElement(By.Id("secimg0"));
    var handle = getIntPtrHandle(chromeDriver);
    SetForegroundWindow(handle);//you need a p/invoke 
    Thread.Sleep(1500);//setting foreground window takes time
    Actions action = new Actions(chromeDriver);
    action.ContextClick(captchaImage).Build().Perform();
    Thread.Sleep(300);
    SendKeys.Send("V");
    var start = Environment.TickCount;
    while (Environment.TickCount - start < 2000)
    {//can't use Thread.Sleep here, alternatively you can use a Timer
          Application.DoEvents();
    }
    SendKeys.SendWait(@"C:\temp\vImage.jpg");
    SendKeys.SendWait("{ENTER}");
}

This is the only way I've found to download a captcha image without losing its quality (for better OCR effects) using Selenium Chrome driver, although the limitation is also obvious.

2

You can block images from being downloaded in Google Chrome using this technique. It runs a Google Chrome extension called "Block Image". This way the image won't be downloaded using chrome, and it's just a matter of downloading the image as normal using its URL & System.Net.WebClient.

Fidel
  • 7,027
  • 11
  • 57
  • 81
2

Based on meceks answer, I use a version of the following with great results to capture the webdriver image.

It creates a base64 jpeg string at 90% quality. To avoid pixelation issues, i draw the image onto a canvas which is larger than what i will be presenting the image on later. The image is therefore up-scaled to best fit a box of 600 pixels while preserving aspect ratios. Since jpeg doesn't support transparency i clear the context with a white background.

var base64string = (driver as IJavaScriptExecutor).ExecuteScript(@"
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');

function getMaxSize(srcWidth, srcHeight, maxWidth, maxHeight) {
    var widthScale = null;
    var heightScale = null;

    if (maxWidth != null)
    {
        widthScale = maxWidth / srcWidth;
    }
    if (maxHeight != null)
    {
        heightScale = maxHeight / srcHeight;
    }

    var ratio = Math.min(widthScale || heightScale, heightScale || widthScale);
    return {
        width: Math.round(srcWidth * ratio),
        height: Math.round(srcHeight * ratio)
    };
}

function getBase64FromImage(img, width, height) {
    var size = getMaxSize(width, height, 600, 600)
    canvas.width = size.width;
    canvas.height = size.height;
    ctx.fillStyle = 'white';
    ctx.fillRect(0, 0, size.width, size.height);
    ctx.drawImage(img, 0, 0, size.width, size.height);
    return canvas.toDataURL('image/jpeg', 0.9);
}

var img = document.querySelector('#foo');
    return getBase64FromImage(img, img.width, img.height);
") as string;

var base64 = base64string.Split(',').Last();
Oddmar Dam
  • 719
  • 9
  • 26
Adam
  • 21
  • 1
  • 4
1

Have you trying to download the image using ImageIO?

String imageUrl = "image.png";
BufferedImage bufferedImage = ImageIO.read(imageUrl);
ImageIO.write(bufferedImage, "png", new File("savedImage.png"));
Johnny
  • 14,397
  • 15
  • 77
  • 118
1

try the following in java:

JavascriptExecutor js = (JavascriptExecutor) driver;                              
String base64string = (String) js.executeScript("var c = document.createElement('canvas');"
                                  + " var ctx = c.getContext('2d');"
                                  + "var img = document.getElementsByTagName('img')[0];"
                                  + "c.height=img.naturalHeight;"
                                  + "c.width=img.naturalWidth;"
                                  + "ctx.drawImage(img, 0, 0,img.naturalWidth, img.naturalHeight);"
                                  + "var base64String = c.toDataURL();"
                                  + "return base64String;");
String[] base64Array = base64string.split(",");

String base64 = base64Array[base64Array.length - 1];

byte[] data = Base64.decode(base64);

ByteArrayInputStream memstream = new ByteArrayInputStream(data);
BufferedImage saveImage = ImageIO.read(memstream);

ImageIO.write(saveImage, "png", new File("C:\\ClaimsData\\downloadspdfs\\" + originalName));
Patrick
  • 5,526
  • 14
  • 64
  • 101
  • Just a sidenote: Of course this way you will loose all the EXIF/meta data of the original photo if that matters to you. – tot Jul 17 '20 at 16:05
1

Based on the Answer of Mehmet Mecek, i've made my own little method, to fetch an Image based on className (because there was no 'id' available) and since every Image i wanted to fetch had the same className, i've used the src attribute content (url to the image) to filter the javascript result to get the specific image i wanted...

src can also be only a partial string (such as the filename), but must be case sensitive to the original in the HTML.

Notice: no error checking included.

worked like a charm.


 public static void LoadImageFromClassAndSrcInfo(IWebDriver webDriver, string className, string partialSrc, string localFile)
        {
            IJavaScriptExecutor js = (IJavaScriptExecutor) webDriver;
            string base64string = js.ExecuteScript(@"
    var c = document.createElement('canvas');
    var ctx = c.getContext('2d');
    var img = Array.prototype.filter.call(document.getElementsByClassName('"+className+@"'), ({ src }) => src.includes('"+ partialSrc +@"') )[0];
    c.height=img.naturalHeight;
    c.width=img.naturalWidth;
    ctx.drawImage(img, 0, 0,img.naturalWidth, img.naturalHeight);
    var base64String = c.toDataURL();
    return base64String;
    ") as string;

            var base64 = base64string.Split(',').Last();

            using (var stream = new MemoryStream(Convert.FromBase64String(base64)))
            {
                using (var bitmap = new Bitmap(stream))
                {
                    bitmap.Save(localFile, ImageFormat.Jpeg);
                }
            }
        }

Markus Doerig
  • 166
  • 1
  • 3
1

Just want to share an experience: I wanted to put a captcha image in a separate form, so that the user could put the text in the field as an alternative to the main webpage. I combined the methods proposed by TH Todorov at https://stackoverflow.com/a/30025029 (answered above) and James Hill, at How to cut a part of image in C# (mentioned above) and came up with the following code. it works perfectly in a case, you dont need to save picture on a drive. I also works for any image in a website. I hope this is of some help.

private Bitmap GetCaptchaImage()
        {
            ITakesScreenshot ssdriver = driver as ITakesScreenshot;
            Screenshot screenshot = ssdriver.GetScreenshot();
            IWebElement captchaImage = driver.FindElement(By.XPath("put the captcha image path here"));

            Point point = captchaImage.Location;
            int width = captchaImage.Size.Width;
            int height = captchaImage.Size.Height;

            Rectangle section = new Rectangle(point, new Size(width, height));
            Bitmap source = new Bitmap(new MemoryStream(screenshot.AsByteArray));

            Bitmap finalCaptchImage = CropImage(source, section);
            return finalCaptchImage;
        }
        private Bitmap CropImage(Bitmap source, Rectangle section)
        {
            Bitmap bmp = new Bitmap(section.Width, section.Height);
            Graphics g = Graphics.FromImage(bmp);
            g.DrawImage(source, 0, 0, section, GraphicsUnit.Pixel);
            return bmp;
        }
A. Porteghali
  • 11
  • 1
  • 3
  • Very nice Abbasali, thank you for sharing your solution - it's very neat – Fidel Sep 04 '20 at 00:49
  • not getting anything in that part of screenshot ;( – Tushar Kshirsagar Apr 05 '21 at 14:54
  • Dear Tushar, first I repeat my comments to say the code I shared here is just about an idea to add a functionality to other answers for working with images which were created from a screenshot. Then I shared the methods from a class in my code. I checked it out again and it works. Because, no detailed information was provided, I guess it maybe either something related to initializing the webdriver , or the address (in this code: xpath) of the image. Please let me know if I can be of any help. – A. Porteghali Jun 10 '21 at 14:30
0
I'm using Selenium & Google Chrome Driver

Talks of selenium.

once in the browser, once with WebClient

Htmlunit ?

Anyways, why don't you use webclient (htmlunit-driver) or pure htmlunit (http://htmlunit.sourceforge.net/). Htmlunit doesn't download images by default.

You can download them on will, as per your requirement.

coding_idiot
  • 13,526
  • 10
  • 65
  • 116
  • Thanks coding, selenium is very capable of doing what htmlunit does. I'm using C# so when referring to WebClient I'm referring to System.Net.WebClient. The reason why I can't use pure System.Net.WebClient is because the page is complex in the sense that it uses jquery a number of times to get the remainder of the content (in this case a jpeg viewer). Selenium lets me get over that hurdle but the down side is that the image is downloaded twice. – Fidel Aug 25 '13 at 08:13