10

To my understanding the ChromeDriver itself doesn't set the background, the CSS does. Therefore if the background is transparent, why am I not getting transparent screenshots?

This is the screenshot of the supposedly transparent website: Transparent background Same screenshot but with a red div in the background to show where the transparency should lie: Red background

Here is my code:

from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from datetime import datetime

options = webdriver.ChromeOptions()
options.add_argument('headless')
driver = webdriver.Chrome(chrome_options=options)

driver.set_window_size(2560, 1600)
driver.get('https://twitter.com/realDonaldTrump/status/516382177798680576')
# driver.execute_script("$('body').append(`<div style='background: red; width: 100%; height: 100%;'></div>`);")
driver.save_screenshot('screenshots/' + str(datetime.now()) + '.png')

driver.quit()

How would I be able to create the transparent version of that screenshot?

*** EDIT *** I made a gist of how I accomplished this. The accepted answer helped me get to the point where I could figure it out and that is what I wanted. This gist, however, is the correct solution to my problem: https://gist.github.com/colecrtr/f58834ff09ab07e3c1164667b753e77a

Cole
  • 1,715
  • 13
  • 23
  • There that line that you have commented, did you try to change to something like this: `driver.execute_script("$('body').css('background-color', 'transparent');")`? Beware that all the div layers have to be `background-color: transparent` in order to have a really transparent background, so you'll have to do some testings and tweaking's with developer tools if it won't work by just changing body background color. Also keep in mind that the page has to have jQuery and I don't think twitter does, but I'm not really sure. – Christos Lytras Oct 12 '17 at 14:51
  • @ChristosLytras I tried setting the `background-color` to `transparent` and it is still resulting in a white background. And Twitter does use jQuery. How would I go about tweaking the developer tools? – Cole Oct 12 '17 at 15:01
  • Just hit F12 when browsing twitter, go to *Elements* tab and then check the styles of each DIV child element of the content for background color properties. – Christos Lytras Oct 12 '17 at 15:04
  • It's a shame the link to the gist is broken. – Brad Root Jul 14 '20 at 21:59
  • 1
    @BradRoot I changed my username recently and that broke the link. I’ve updated it to my new username :-) – Cole Jul 14 '20 at 22:01

4 Answers4

10

One way would be to convert each white pixel to a transparent pixel from the screenshot by setting the alpha byte to 0:

from selenium import webdriver
from PIL import Image
from io import BytesIO # python 3
import numpy as np

def remove_color(img, rgba):
  data = np.array(img.convert('RGBA'))        # rgba array from image
  pixels = data.view(dtype=np.uint32)[...,0]  # pixels as rgba uint32
  data[...,3] = np.where(pixels == np.uint32(rgba), np.uint8(0), np.uint8(255))  # set alpha channel
  return Image.fromarray(data)

driver = webdriver.Chrome()
driver.get("http://www.bbc.co.uk/news")

# take screenshot with a transparent background
with Image.open(BytesIO(driver.get_screenshot_as_png())) as img :
  with remove_color(img, 0xffffffff) as img2:
    img2.save(r"C:\temp\screenshot.png")

However you may end up with some unexpected transparent pixels if the page content has some white pixels and the antialiassing will probably be visible.

Another solution is to use the DevTool API with Chrome to exclude the background from the screenshot:

from selenium import webdriver
import json

def send(cmd, params={}):
  resource = "/session/%s/chromium/send_command_and_get_result" % driver.session_id
  url = driver.command_executor._url + resource
  body = json.dumps({'cmd':cmd, 'params': params})
  response = driver.command_executor._request('POST', url, body)
  if response['status']: raise Exception(response.get('value'))
  return response.get('value')

options = webdriver.ChromeOptions()
options.add_argument("disable-gpu")
options.add_argument("disable-infobars")

driver = webdriver.Chrome(chrome_options=options)
driver.get("http://www.bbc.co.uk/news")

# take screenshot with a transparent background
send("Emulation.setDefaultBackgroundColorOverride", {'color': {'r': 0, 'g': 0, 'b': 0, 'a': 0}})
driver.get_screenshot_as_file(r"C:\temp\screenshot.png")
send("Emulation.setDefaultBackgroundColorOverride")  # restore
Florent B.
  • 41,537
  • 7
  • 86
  • 101
1

A png can have transparent pixels, but a screenshot cannot. Whenever you render something you mix the rendering looking at transparency levels of layers, but the combine layer will always have a background.

A screenshot is of what has been rendered on screen and it can never be transparent. How do you display a true transparent image on a desktop? You can't because the background of desktop or something else will always have to be there.

So what you are asking has nothing to do with Chrome, ChromeDriver. Any screenshot taking tool cannot take a transparent screenshot, without you telling it what to mask.

Even tools like Photoshop use special way (grey color boxes with small changes) to show transparent background, but if you use a screenshot tool to capture that image the result would be a image with actual pixel and no transparency like below

Transparent image

Tarun Lalwani
  • 142,312
  • 9
  • 204
  • 265
  • Great points and I understand this but using a PhantomJS driver will result in a transparent image as long as there is no defined background or the background itself is defined as transparent. Therefore I know it's possible, it's just a matter of how with ChromeDriver as other drivers don't render the page correctly. – Cole Oct 10 '17 at 19:26
  • Try with chrome headless and see if it changes, because all the ones which actually render on screen will give you a background color. PhantomJS being a in memory renderer doesn't need to have a background container to render on to and loose transparency – Tarun Lalwani Oct 10 '17 at 19:29
  • A screenshot can have a transparent background. For instance [puppeteer](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagescreenshotoptions) provides the option. With Selenium It's possible by removing the background from the PNG or by calling `Emulation.setDefaultBackgroundColorOverride` with a custom command with Chrome. – Florent B. Oct 12 '17 at 17:10
  • @FlorentB., this is using Chrome debugger APIs and not Selenium – Tarun Lalwani Oct 12 '17 at 17:17
  • The Chrome debugger APIs can be called with Selenium. – Florent B. Oct 12 '17 at 17:18
  • @FlorentB., would love to see an example on how that is achieved. Do you have any links or code? I was not aware, that a Selenium attached browser lets you execute ChromeDebugger APIs – Tarun Lalwani Oct 12 '17 at 17:21
  • https://stackoverflow.com/questions/45199076/take-full-page-screen-shot-in-chrome-with-selenium/45201692#45201692 – Florent B. Oct 12 '17 at 17:26
  • Thanks! this is awesome. You have a +1 from me :-) – Tarun Lalwani Oct 12 '17 at 18:11
  • @Cole, does this work for you then? Using https://stackoverflow.com/questions/45199076/take-full-page-screen-shot-in-chrome-with-selenium/45201692#45201692 – Tarun Lalwani Oct 12 '17 at 18:15
0

I know that this has already been answered, but using the accepted answer didn't work in my case and I had to make a small adjustment to it. Extending accepted answer's code, it turns out, in my case I had to add background to the body first before it could be replaced:

from selenium import webdriver
import json

def send(cmd, params={}):
  resource = "/session/%s/chromium/send_command_and_get_result" % driver.session_id
  url = driver.command_executor._url + resource
  body = json.dumps({'cmd':cmd, 'params': params})
  response = driver.command_executor._request('POST', url, body)
  return response.get('value')

options = webdriver.ChromeOptions()

options.add_argument('headless')
options.add_argument('disable-gpu')
options.add_argument('disable-infobars')

driver = webdriver.Chrome(chrome_options=options)
driver.get("http://www.bbc.co.uk/news")

# take screenshot with a transparent background
driver.execute_script("document.getElementsByTagName('body')[0].style.backgroundColor = 'transparent';") # I had to add this line
send("Emulation.setDefaultBackgroundColorOverride", {'color': {'r': 0, 'g': 0, 'b': 0, 'a': 0}})
driver.get_screenshot_as_file(r"screenshot.png")
send("Emulation.setDefaultBackgroundColorOverride")  # restore

Hope this could help those who face the same issue

Damzaky
  • 6,073
  • 2
  • 11
  • 16
0

For those attempting to do this in Laravel Dusk or in PHP using Facebook's Web Driver, you have to call the executeCustomCommand function:

$browser->visit($url)->script("document.getElementsByTagName('body')[0].style.backgroundColor = 'transparent'");

$browser->driver->executeCustomCommand('/session/:sessionId/chromium/send_command', 'POST', [
    'cmd' => 'Emulation.setDefaultBackgroundColorOverride',
    'params' => ['color' => ['r' => 0, 'g' => 0, 'b' => 0, 'a' => 0]],
]);

$browser->screenshot('screenshot.png');
Steve Bauman
  • 8,165
  • 7
  • 40
  • 56