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. However, not being able to take a screenshot due to security policies is a separate topic that needs to be discussed separately.
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} = 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
video.play();
// 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.srcObject = stream;
document.body.appendChild(video);
};
this.handleError = function(e) {
console.log(e);
};
desktopCapturer.getSources({ types: ['window', 'screen'] }).then(async sources => {
console.log(sources);
for (const source of sources) {
// Filter: main screen
if ((source.name === "Entire screen") || (source.name === "Screen 1") || (source.name === "Screen 2")) {
try{
const stream = await navigator.mediaDevices.getUserMedia({
audio: false,
video: {
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: source.id,
minWidth: 1280,
maxWidth: 4000,
minHeight: 720,
maxHeight: 4000
}
}
});
_this.handleStream(stream);
} catch (e) {
_this.handleError(e);
}
}
}
});
}
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:
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} = 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) => {
// 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
video.play();
// 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.srcObject = stream;
document.body.appendChild(video);
};
this.handleError = function(e) {
console.log(e);
};
desktopCapturer.getSources({ types: ['window', 'screen'] }).then(async sources => {
console.log(sources);
for (const source of sources) {
// Filter: main screen
if (source.name === document.title) {
try{
const stream = await navigator.mediaDevices.getUserMedia({
audio: false,
video: {
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: source.id,
minWidth: 1280,
maxWidth: 4000,
minHeight: 720,
maxHeight: 4000
}
}
});
_this.handleStream(stream);
} catch (e) {
_this.handleError(e);
}
}
}
});
}
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:
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:
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:
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:
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 !