Creating screenshots of your app or the screen in Electron framework

Creating screenshots of your app or the screen in Electron framework

Although to require the creation of a screenshot in your app is not so usual, know how to do it won't hurt you. This feature is really useful for every application that want, for example, a detailed error report! a console error message may not help in a lot of cases, however, an image could save the day.

In this article, you'll learn how to create a screenshot in Electron from differents areas in the Desktop easily using the desktopCapture built-in component.

Fullscreen screenshot

In case you need to take a screenshot of the entire screen, using the desktopCapturer module will do the job for you. To handle the fullscreenshot, use the following function:

const {desktopCapturer, screen} = require('electron');

/**
 * Create a screenshot of the entire screen using the desktopCapturer module of Electron.
 *
 * @param callback {Function} callback receives as first parameter the base64 string of the image
 * @param imageFormat {String} Format of the image to generate ('image/jpeg' or 'image/png')
 **/
function fullscreenScreenshot(callback, imageFormat) {
    var _this = this;
    this.callback = callback;
    imageFormat = imageFormat || 'image/jpeg';
    
    this.handleStream = (stream) => {
        // Create hidden video tag
        var video = document.createElement('video');
        video.style.cssText = 'position:absolute;top:-10000px;left:-10000px;';
        // Event connected to stream
        video.onloadedmetadata = function () {
            // Set video ORIGINAL height (screenshot)
            video.style.height = this.videoHeight + 'px'; // videoHeight
            video.style.width = this.videoWidth + 'px'; // videoWidth

            // Create canvas
            var canvas = document.createElement('canvas');
            canvas.width = this.videoWidth;
            canvas.height = this.videoHeight;
            var ctx = canvas.getContext('2d');
            // Draw video on canvas
            ctx.drawImage(video, 0, 0, canvas.width, canvas.height);

            if (_this.callback) {
                // Save screenshot to base64
                _this.callback(canvas.toDataURL(imageFormat));
            } else {
                console.log('Need callback!');
            }

            // Remove hidden video tag
            video.remove();
            try {
                // Destroy connect to stream
                stream.getTracks()[0].stop();
            } catch (e) {}
        }
        video.src = URL.createObjectURL(stream);
        document.body.appendChild(video);
    };

    this.handleError = function(e) {
        console.log(e);
    };

    // Filter only screen type
    desktopCapturer.getSources({types: ['screen']}, (error, sources) => {
        if (error) throw error;
        // console.log(sources);
        for (let i = 0; i < sources.length; ++i) {
            console.log(sources);
            // Filter: main screen
            if (sources[i].name === "Entire screen") {
                navigator.webkitGetUserMedia({
                    audio: false,
                    video: {
                        mandatory: {
                            chromeMediaSource: 'desktop',
                            chromeMediaSourceId: sources[i].id,
                            minWidth: 1280,
                            maxWidth: 4000,
                            minHeight: 720,
                            maxHeight: 4000
                        }
                    }
                }, this.handleStream, this.handleError);

                return;
            }
        }
    });
}

The function will use the desktopCapture module to create a screenshot of the entire screen. It expect as first parameter a function (the callback) that will be invoked when the screenshot is ready to be manipulated. Optionally, you can provide the format of the result image as second parameter with the content type (image/png or image/jpeg).

Usage

The usage of this function is simple, you can include it in your html document and use it from there:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>My Electron Screenshot App</title>
    </head>
    <body>
        <p>Testing screenshots in Electron :3</p>
        <img id="my-preview"/>
        <input id="trigger" value="Fullscreen screenshot" type="button"/>

        <script>
            //* Here the fullscreenScreenshot function *//
            
            document.getElementById("trigger").addEventListener("click", function(){
                fullscreenScreenshot(function(base64data){
                    // Draw image in the img tag
                    document.getElementById("my-preview").setAttribute("src", base64data);
                },'image/png');
            },false);
        </script>
    </body>
</html>

The execution of the previous snippet in electron, should produce something similar to:

Electron fullscreen screenshot

Awesome, sweet and simple isn't?

Creating a screenshot of your Electron app

To create a screenshot of only your app, we are going to use the same method but with a couple of modifications:

const {desktopCapturer, screen} = require('electron');

/**
 * Create a screenshot of your electron app. You can modify which process to render in the conditional line #61.
 * In this case, filtered using the title of the document.
 *
 * @param callback {Function} callback receives as first parameter the base64 string of the image
 * @param imageFormat {String} Format of the image to generate ('image/jpeg' or 'image/png')
 **/
function appScreenshot(callback,imageFormat) {
     var _this = this;
     this.callback = callback;
     imageFormat = imageFormat || 'image/jpeg';
     
     this.handleStream = (stream) => {
         // console.log('stream',stream);
         // Create hidden video tag
         var video = document.createElement('video');
         video.style.cssText = 'position:absolute;top:-10000px;left:-10000px;';
         // Event connected to stream
         video.onloadedmetadata = function () {
             // Set video ORIGINAL height (screenshot)
             video.style.height = this.videoHeight + 'px'; // videoHeight
             video.style.width = this.videoWidth + 'px'; // videoWidth
 
             // Create canvas
             var canvas = document.createElement('canvas');
             canvas.width = this.videoWidth;
             canvas.height = this.videoHeight;
             var ctx = canvas.getContext('2d');
             // Draw video on canvas
             ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
 
             if (_this.callback) {
                 // Save screenshot to jpg - base64
                 _this.callback(canvas.toDataURL(imageFormat));
             } else {
                 console.log('Need callback!');
             }
 
             // Remove hidden video tag
             video.remove();
             try {
                 // Destroy connect to stream
                 stream.getTracks()[0].stop();
             } catch (e) {}
         }
         video.src = URL.createObjectURL(stream);
         document.body.appendChild(video);
     };
 
     this.handleError = function(e) {
         console.log(e);
     };
 
     desktopCapturer.getSources({types: ['window', 'screen']}, (error, sources) => {
         if (error) throw error;
         // console.log(sources);
         for (let i = 0; i < sources.length; ++i) {
             console.log(sources);
             // Filter: main screen
             if (sources[i].name === document.title) {
                 navigator.webkitGetUserMedia({
                     audio: false,
                     video: {
                         mandatory: {
                             chromeMediaSource: 'desktop',
                             chromeMediaSourceId: sources[i].id,
                             minWidth: 1280,
                             maxWidth: 4000,
                             minHeight: 720,
                             maxHeight: 4000
                         }
                     }
                 }, this.handleStream, this.handleError);
                 return
             }
         }
     });
 }

In this case, in the desktopCapturer.getSources method we are going to load both window and screen. This will provide an object with a structure similar to:

List sources chrome and electron

The object by itself contains all the active windows in the chromium process (google chrome and electron apps). You can use the identifier of every object (name) to choose which process is going to be used to create the screenshot.

In this case, as we want to create a screenshot of our app, you need to filter by the title of the app ("My Electron Screenshot App") using the document.title property as used in the snippet to identify your app.

Usage

The following snippet shows how to create a screenshot of your own app easily using the appScreenshot function:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>My Electron Screenshot App</title>
    </head>
    <body>
        <p>Testing screenshots in Electron :3</p>
        <img id="my-preview"/>
        <input id="trigger" value="Fullscreen screenshot" type="button"/>

        <script>
            //* Here the appScreenshot function *//
            
            document.getElementById("trigger").addEventListener("click", function(){
                appScreenshot(function(base64data){
                    // Draw image in the img tag
                    document.getElementById("my-preview").setAttribute("src", base64data);
                },'image/png');
            },false);
        </script>
    </body>
</html>

The execution of the previous snippet in electron, should produce something similar to:

Electron application screenshot

Note: if you face a black screen (that happen to us too, as you can see the app has the Windows 10 style but the screenshot has Aero style due to the --disable-d3d11 flag to prevent the black screen) using this method, read more about the problem and how to solve it in the Known issues area.

Screenshot with specified dimensions

If you want to specify the coordinates of the screen where the screenshot should be taken, then i'm sorry for disappointing you, because that's not possible natively. However, you can use a little trick in order to accomplish your goal. Create a fullscreenshot and crop it using a third party image manipulation module!

In the following example, we are going to take a fullscreenshot (provided by the first function) and we are going to crop the area of our app (the electron app) with the location of the window according to the screen using the jimp module which doesn't require any dependency and works with pure javascript:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Working with Screnshots!</title>
</head>
<body>
    <div>
        <img id="image-preview" />
        <input type="button" id="create-screenshot" />
    </div>
    <script>
        /**
         * Create a screenshot of our app from a  fullscreenshot cropping it with Jimp module !
         */
        document.getElementById("create-screenshot").addEventListener("click", function() {
            var Jimp = require("jimp");

            fullscreenScreenshot(function(base64data){
                // add to buffer base64 image instead of saving locally in order to manipulate with Jimp
                var encondedImageBuffer = new Buffer(base64data.replace(/^data:image\/(png|gif|jpeg);base64,/,''), 'base64');

                var height = window.innerHeight;
                var width = window.innerWidth;
                var distanceX = window.screenLeft;
                var distanceY = window.screenTop;
                var screenDimensions = screen.getPrimaryDisplay().size;
                var screenHeight = screenDimensions.height;
                var screenWidth = screenDimensions.width;

                Jimp.read(encondedImageBuffer, function (err, image) {
                    if (err) throw err;

                    // Show the original width and height of the image in the console
                    console.log(image.bitmap.width, image.bitmap.height);

                    // Resize the image to the size of the screen
                    image.resize(screenWidth, screenHeight)
                    // Crop image according to the coordinates
                    // add some margin pixels for this example
                    image.crop(distanceX + 10, distanceY - 10, width + 10, height + 50)
                    // Get data in base64 and show in img tag
                    .getBase64('image/jpeg', function(err,base64data){
                        document.getElementById("image-preview").setAttribute("src", base64data);
                        //console.log(data);
                    });
                });
            },"image/jpeg");
        }, false);
    </script>
</body>
</html>

And the result should be something similar to:

Electron screenshop with specific dimensions cropped

Note that this module has been written in pure Javascript and doesn't require any native dependency, therefore it's something slow. Is up to you to implement any native module in order to crop the image to your needs.

Known issues

In some devices you may face the black screen issue, reported in this issue in Github:

Black screen electron screenshot

The only known and working solution is to start your application with some extra arguments to prevent the usage of DX11 or the GPU acceleration using the flags wheter --disable-d3d11 or --disable-gpu.

In the following example, we'll add the flag in the package.json of the project in the start instruction of the scripts element (note the --disable-d3d11 parameter at the initialization of the app):

{
    "name": "electron-quick-start",
    "version": "1.0.0",
    "description": "A minimal Electron application",
    "main": "main.js",
    "scripts": {
        "start": "electron . --disable-d3d11"
    },
    "repository": {
        "type": "git",
        "url": "git+https://github.com/electron/electron-quick-start.git"
    },
    "keywords": [
        "Electron",
        "quick",
        "start",
        "tutorial"
    ],
    "author": "GitHub",
    "license": "CC0-1.0",
    "bugs": {
        "url": "https://github.com/electron/electron-quick-start/issues"
    },
    "homepage": "https://github.com/electron/electron-quick-start#readme",
    "devDependencies": {
        "electron-prebuilt": "^1.2.0"
    },
    "dependencies": {
        "electron-dl": "^1.3.0"
    }
}

Then start the app as always using npm start and that should do the trick (in debug mode that should simulate a myapp.exe --argument instruction), remember to try only with 1 and then with both till it works.

Have fun !

Become a more social person