Nano Hash - криптовалюты, майнинг, программирование

Как делать скриншоты Retina с помощью Xvfb и Selenium

Я хочу сделать несколько скриншотов своего гибридного приложения для автоматического подключения iTunes. Я использую Ubuntu 14.04. хромдрайвер 2.15.322448

Делать скриншоты автоматически легко с Selenium и Xvfb. Но получить скриншоты retina непросто.

Я начал свой Xvfb с более высоким разрешением:

/usr/bin/Xvfb :99 -screen 0 2000x2000x24 -dpi 200

Когда я проверяю информацию об отображении, все кажется правильным:

xdpyinfo -display :99

...
screen #0:
  dimensions:    2000x2000 pixels (254x254 millimeters)
  resolution:    200x200 dots per inch
  depths (6):    24, 1, 4, 8, 16, 32
...

Затем я запускаю свой хромированный драйвер следующим образом

private WebDriver getChromeDriver ( Phone phone )
{
    Map<String, Object> deviceMetrics = new HashMap<String, Object>();
    deviceMetrics.put("width", 320);
    deviceMetrics.put("height", 460);
    deviceMetrics.put("pixelRatio", 2);
    Map<String, Object> mobileEmulation = new HashMap<String, Object>();
    mobileEmulation.put("deviceMetrics", deviceMetrics);
    mobileEmulation.put("userAgent", "iphone4");

    ChromeDriverService cds = new ChromeDriverService.Builder().withEnvironment(ImmutableMap.of("DISPLAY", ":99")).build();

    Map<String, Object> chromeOptions = new HashMap<String, Object>();
    chromeOptions.put("mobileEmulation", mobileEmulation);
    DesiredCapabilities capabilities = DesiredCapabilities.chrome();
    capabilities.setCapability(ChromeOptions.CAPABILITY, chromeOptions);
    WebDriver driver = new ChromeDriver(cds, capabilities);
    return driver;
}

и после какого-то другого скучного кода делаю скриншот:

 File srcFile = ( (TakesScreenshot) driver ).getScreenshotAs(OutputType.FILE);

Это не работает. Скриншот в обычном dpi. Таким образом, захваченное изображение веб-сайта имеет размер всего 320x460, а не 640x960, как должно быть.

Я установил точку останова непосредственно перед тем, как был сделан снимок экрана, и сбросил кадровый буфер следующим образом:

export DISPLAY=:99 
xwd -root -silent | xwdtopnm |pnmtojpeg > screen.jpg

Результат сброса xwd содержимого виртуального фреймбуфера

Как видите, строка заголовка отображается с учетом более высокого разрешения, а остальная часть окна браузера — нет.

Итак, как я могу запустить хромированный драйвер с большим количеством точек на дюйм, чтобы делать скриншоты сетчатки? Является ли это возможным?


  • вы нашли решение этого? Я застрял в точно такой же проблеме, был бы признателен, если бы вы поделились своим опытом со снимками экрана с разрешением xvfb и retina. 03.07.2016
  • Я разместил ответ ниже. Я надеюсь, что это помогает. 04.07.2016

Ответы:


1

Если вы просто хотите взять несколько экранных хостов, вы можете использовать безголовый инструмент Google Chrome. Например, сделать скриншот сетчатки так же просто, как

$ google-chrome --headless --hide-scrollbars --disable-gpu \
                --screenshot --force-device-scale-factor=2 \
                --window-size=750,1334 https://www.kicktipp.de/
02.11.2017
  • --force-device-scale-factor=2 исправил это для меня, большое спасибо! 10.12.2019

  • 2

    Я столкнулся с той же проблемой и все еще застрял, но следующее может быть полезно. Это позволило мне исключить xvfb или chrome, подключив соединение VNC к кадровому буферу xvfb.

    #!/bin/bash
    export GEOMETRY="$SCREEN_WIDTH""x""$SCREEN_HEIGHT""x""$SCREEN_DEPTH"
    
    function shutdown {
      kill -s SIGTERM $NODE_PID
      wait $NODE_PID
    }
    
    sudo -E -i -u seluser \
      DISPLAY=$DISPLAY \
      xvfb-run --server-args="$DISPLAY -screen 0 $GEOMETRY -dpi 300 -ac +extension RANDR" \
      java -jar /opt/selenium/selenium-server-standalone.jar &
    NODE_PID=$!
    
    trap shutdown SIGTERM SIGINT
    for i in $(seq 1 10)
    do
      xdpyinfo -display $DISPLAY >/dev/null 2>&1
      if [ $? -eq 0 ]; then
        break
      fi
      echo Waiting xvfb...
      sleep 0.5
    done
    
    fluxbox -display $DISPLAY &
    
    x11vnc -forever -usepw -shared -rfbport 5900 -display $DISPLAY &
    
    wait $NODE_PID
    

    После входа в VNC google-chrome GUI можно загрузить из терминала. Переход на веб-страницы подтверждает, что Chrome отображает страницы с правильным DPI. Скриншот http://i.stack.imgur.com/iEjo0.jpg

    Я бы очень хотел, чтобы это тоже работало, поэтому, пожалуйста, обращайтесь, если у вас есть какие-либо новые разработки. Я использовал https://registry.hub.docker.com/u/selenium/standalone-chrome-debug/ Кстати.

    15.07.2015

    3

    Я переключился на Firefox, и у меня сработал следующий код. Но на данный момент это не так, поскольку селен не работает нормально с моей версией Firefox 47, см. https://github.com/SeleniumHQ/selenium/issues/2257 Поэтому я не могу протестировать этот код прямо сейчас, но в прошлый раз я смог получить с его помощью скриншоты Retina:

    package de.kicktipp.screenshots.stackoverflow;
    
    import java.awt.Image;
    import java.awt.image.BufferedImage;
    import java.io.File;
    import java.io.IOException;
    import java.util.ArrayList;
    
    import javax.imageio.ImageIO;
    
    import org.openqa.selenium.Dimension;
    import org.openqa.selenium.JavascriptExecutor;
    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.firefox.FirefoxBinary;
    import org.openqa.selenium.firefox.FirefoxDriver;
    import org.openqa.selenium.firefox.FirefoxProfile;
    
    public class ScreenshotMaker
    {
        private PhoneList           phoneList   = new PhoneList();
    
        private static final String HOST        = "https://m.kicktipp.de";
        private static final String PATH        = "/";
    
        private File                resultDirectory;
        private int                 filenumber  = 0;
    
        public static void main ( String[] args ) throws Exception
        {
            ScreenshotMaker screenshotMaker = new ScreenshotMaker();
            screenshotMaker.run();
        }
    
        public WebDriver getDriver ( Phone phone, Display display )
        {
            FirefoxProfile profile = new FirefoxProfile();
            // profile.setPreference("layout.css.devPixelsPerPx", "2.0");
            // Ansonsten erscheint ein hässliches Popup welches Reader Funktion
            // anbietet
            profile.setPreference("reader.parse-on-load.enabled", false);
            profile.setPreference("xpinstall.signatures.required", false);
            FirefoxBinary firefoxBinary = new FirefoxBinary();
            firefoxBinary.setEnvironmentProperty("DISPLAY", display.getDisplayNumberString());
            FirefoxDriver firefoxDriver = new FirefoxDriver(firefoxBinary, profile);
            firefoxDriver.manage().window().setSize(new Dimension(phone.getWidth(), display.getHeight()));
            return firefoxDriver;
        }
    
        private void run ( ) throws Exception
        {
            mkdir();
            for (Phone phone : phoneList)
            {
                WebDriver driver = null;
                Display display = null;
                try
                {
                    display = new Display(phone.getDpiFaktor());
                    driver = getDriver(phone, display);
                    System.out.println(phone.getName());
                    filenumber = 0;
                    System.out.println("");
                    System.out.println("Generating Screenshots for " + phone.getName());
                    System.out.println("-----------------------------------------------------------------------------");
                    driver.get(HOST + "/");
                    shot(display, driver, PATH, phone);
                }
                finally
                {
                    if (driver != null)
                    {
                        driver.quit();
                    }
                    if (display != null)
                    {
                        display.shutdown();
                    }
                }
            }
            System.out.println("");
            System.out.println("-----------------------------------------------------------------------------");
            System.out.println("Finished.");
    
        }
    
        private void mkdir ( ) throws IOException
        {
            File targetDir = targetDir();
            resultDirectory = new File(targetDir, "results");
            resultDirectory.mkdir();
            System.out.println("Writing screenshots to " + resultDirectory.getCanonicalPath());
        }
    
        public File targetDir ( )
        {
            String relPath = getClass().getProtectionDomain().getCodeSource().getLocation().getFile();
            File targetDir = new File(relPath + "../..");
            if (!targetDir.exists())
            {
                targetDir.mkdir();
            }
            return targetDir;
        }
    
        private void shot ( Display display, WebDriver driver, String path, Phone phoneSpec ) throws Exception
        {
            String url = getUrl(path);
            driver.get(url);
            scrollToRemoveScrollbars(driver);
            // Selenium screenshot doesn't work, we are dumping the framebuffer
            // directly
            File srcFile = display.captureScreenshot();
            moveFile(srcFile, phoneSpec);
        }
    
        private void scrollToRemoveScrollbars ( WebDriver driver ) throws Exception
        {
            JavascriptExecutor js = (JavascriptExecutor) driver;
            js.executeScript("window.scrollTo(0,20);");
            js.executeScript("window.scrollTo(0,0);");
            Thread.sleep(800);
        }
    
        private String getUrl ( String path )
        {
            StringBuffer url = new StringBuffer(HOST);
            url.append(path);
            return url.toString();
        }
    
        private void moveFile ( File srcFile, Phone phone ) throws Exception
        {
            String filename = phone.getFilename(filenumber);
            File file = new File(resultDirectory, filename);
            if (file.exists())
            {
                file.delete();
            }
            crop(srcFile, file, phone);
            System.out.println(filename);
        }
    
        private void crop ( File srcFile, File targetFile, Phone phone ) throws Exception
        {
            int width = phone.getPixelWidth();
            int height = phone.getPixelHeight();
            int yStart = 71 * phone.getDpiFaktor();
            Image orig = ImageIO.read(srcFile);
            BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
            bi.getGraphics().drawImage(orig, 0, 0, width, height, 0, yStart, width, height + yStart, null);
            ImageIO.write(bi, "png", targetFile);
        }
    }
    
    class PhoneList extends ArrayList<Phone>
    {
        private static final Phone  IPHONE_4        = new Phone(320, 460, "iphone4", 2);
        private static final Phone  IPHONE_5        = new Phone(320, 548, "iphone5", 2);
        private static final Phone  IPHONE_6        = new Phone(375, 667, "iphone6", 2);
        private static final Phone  IPAD            = new Phone(1024, 748, "ipad", 2);
        private static final Phone  IPHONE_6_PLUS   = new Phone(414, 736, "iphone6plus", 3);
        private static final Phone  AMAZON          = new Phone(480, 800, "amazon", 1);
    
        public PhoneList ()
        {
            add(AMAZON);
            add(IPHONE_4);
            add(IPHONE_5);
            add(IPHONE_6);
            add(IPAD);
            add(IPHONE_6_PLUS);
        }
    }
    
    class Phone
    {
        private int     width       = 0;
        private int     height      = 0;
        private String  name        = "";
        private int     dpiFaktor   = 2;
    
        public Phone ( int width, int height, String name, int dpiFaktor )
        {
            this.width = width;
            this.height = height;
            this.name = name;
            this.dpiFaktor = dpiFaktor;
        }
    
        public int getWidth ( )
        {
            return width;
        }
    
        public int getHeight ( )
        {
            return height;
        }
    
        public int getPixelWidth ( )
        {
            return width * dpiFaktor;
        }
    
        public int getPixelHeight ( )
        {
            return height * dpiFaktor;
        }
    
        public int getDpiFaktor ( )
        {
            return dpiFaktor;
        }
    
        public String getName ( )
        {
            return name;
        }
    
        public Dimension getDimension ( )
        {
            return new Dimension(width, height);
        }
    
        public String getFilename ( int number )
        {
            String dimension = getPixelWidth() + "x" + getPixelHeight();
            return name + "-" + dimension + "-" + number + ".png";
        }
    }
    
    class Display
    {
        private static final int    HEIGHT                  = 5000;
        private static final int    WIDTH                   = 5000;
        private static String       XVFB                    = "/usr/bin/Xvfb";
        private static String       DISPLAY_NUMBER_STRING   = ":99";
        private static String       SCREEN_SIZE             = " -screen 0 " + WIDTH + "x" + HEIGHT + "x24";
        private static String       XVFB_COMMAND            = XVFB + " " + DISPLAY_NUMBER_STRING + SCREEN_SIZE + " -dpi ";
        private static int          baseDpi                 = 100;
        private Process             p;
    
        public Display ( int dpiFaktor ) throws IOException, InterruptedException
        {
            checkExecutable();
            int dpi = baseDpi * dpiFaktor;
            String cmd = XVFB_COMMAND + dpi;
            p = Runtime.getRuntime().exec(cmd);
            Thread.sleep(1000);
            try
            {
                int exitValue = p.exitValue();
                String msgTemplate = "ERROR: Exit Value: %s. Display konnte nicht gestartet werden. Läuft ein Display noch auf %s ?";
                String msg = String.format(msgTemplate, exitValue, DISPLAY_NUMBER_STRING);
                throw new IllegalStateException(msg);
            }
            catch (IllegalThreadStateException e)
            {
                // Das ist gut, der Prozess ist noch nicht beendet.
                System.out.println("Switched on display at " + dpi + "dpi with command " + cmd);
                return;
            }
        }
    
        private void checkExecutable ( )
        {
            File file = new File(XVFB);
            if (!file.canExecute())
            {
                System.err.println("Xvfb is not installed at " + XVFB);
                System.err.println("Install Xvfb by runing");
                System.err.println("apt-get install xvfb");
            }
        }
    
        public File captureScreenshot ( ) throws IOException, InterruptedException
        {
            File tempFile = File.createTempFile("screenshots", ".png");
            String absolutePath = tempFile.getAbsolutePath();
            String cmd = "import -window root " + absolutePath;
            String[] env = new String[] { "DISPLAY=" + DISPLAY_NUMBER_STRING };
            Process exec = Runtime.getRuntime().exec(cmd, env);
            exec.waitFor();
            return tempFile;
        }
    
        public void shutdown ( ) throws IOException, InterruptedException
        {
            p.destroy();
            try
            {
                Thread.sleep(1000);
                int exitValue = p.exitValue();
                System.out.println("Display was switched off. ExitValue: " + exitValue);
            }
            catch (IllegalThreadStateException e)
            {
                // Das ist nicht gut, der Prozess sollte beendet sein.
                // Kill it:
                p = Runtime.getRuntime().exec("pkill Xvfb");
            }
        }
    
        public String getDisplayNumberString ( )
        {
            return DISPLAY_NUMBER_STRING;
        }
    
        public int getHeight ( )
        {
            return HEIGHT;
        }
    }
    

    А это мой помпон:

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <groupId>de.kicktipp</groupId>
        <artifactId>screenshots</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>screenshots</name>
        <properties>
            <jdk.version>1.7</jdk.version>
            <maven.version>3.0</maven.version>
            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <selenium-java.version>2.53.1</selenium-java.version>
        </properties>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <source>${jdk.version}</source>
                        <target>${jdk.version}</target>
                    </configuration>
                </plugin>
            </plugins>
        </build>
        <dependencies>
            <dependency>
                <groupId>org.seleniumhq.selenium</groupId>
                <artifactId>selenium-java</artifactId>
                <version>${selenium-java.version}</version>
            </dependency>
        </dependencies>
    </project>
    
    04.07.2016
    Новые материалы

    Кластеризация: более глубокий взгляд
    Кластеризация — это метод обучения без учителя, в котором мы пытаемся найти группы в наборе данных на основе некоторых известных или неизвестных свойств, которые могут существовать. Независимо от..

    Как написать эффективное резюме
    Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

    Как я автоматизирую тестирование с помощью Jest
    Шутка для победы, когда дело касается автоматизации тестирования Одной очень важной частью разработки программного обеспечения является автоматизация тестирования, поскольку она создает..

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

    Понимание расстояния Вассерштейна: мощная метрика в машинном обучении
    В обширной области машинного обучения часто возникает необходимость сравнивать и измерять различия между распределениями вероятностей. Традиционные метрики расстояния, такие как евклидово..

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..