electron: scale factor error in webview/webcontents.capturePage
- Electron version: 1.4.13
- Operating system: MacOS 10.12.2 running on MacBook Pro (Retina, 13-inch, Early 2015)
Expected behavior
webview.capturePage() without specifying a dimension should return an image with the full viewable area on a Retina display. The dimensions of the resulting image should be either the actual pixels of the viewable area or perhaps the actual pixels * the device scale factor.
Specifying a dimension should return the viewable area of that dimension with the resulting image being sized correctly (and consistently with the use case of no rect specified).
Actual behavior
Calling webview.capturePage() without specifying a dimension returns in image that is the pixel size of the viewable area * the scale factor the area covered by the viewable area divided by the scale factor.
For example, on a webview that is 524 pixels wide, the resulting image of capturePage() is 1048 pixels wide, but only contains the image data from the first 262 pixels. That image is then upsized by the scale factor twice to get to the 1048 width. Not only does this not capture the full viewable page, it also introduces significant scaling artifacts.
Similar results occur if a rect is specified to capturePage.
How to reproduce
On a Retina display, create a page with a webview that loads some web site:
<webview id="webview" src="http://www.msn.com/" ></webview>
Capture the page:
// used to get the viewable dimensions of the webview
function getWebviewMeta(cb) {
var code = "var r = {}; \
r.totalHeight = document.body.offsetHeight; \
r.pageHeight = window.innerHeight; \
r.pageWidth = window.innerWidth; \
r;";
webview.executeJavaScript(code, false, function(r) {
let webviewMeta = {};
webviewMeta.captureHeight = r.pageHeight;
webviewMeta.captureWidth = r.pageWidth;
cb(webviewMeta);
});
}
getWebviewMeta(function(webviewMeta) {
let captureRect = {
x: 0, y: 0,
width: webviewMeta.captureWidth,
height: webviewMeta.captureHeight
};
webview.capturePage((img) => {
console.log("image size: ", img.getSize());
let jpgFile = img.toJPEG(90);
// save file as testDefault.jpg, I used jetpack.writeAsync
});
webview.capturePage(captureRect, (img) => {
console.log("image size: ", img.getSize());
let jpgFile = img.toJPEG(90);
// save file as testSetViewSize.jpg, I used jetpack.writeAsync
});
captureRect.width = +captureRect.width * electron.screen.getPrimaryDisplay().scaleFactor;
captureRect.height = +captureRect.height * electron.screen.getPrimaryDisplay().scaleFactor;
webview.capturePage(captureRect, (img) => {
console.log("image size: ", img.getSize());
let jpgFile = img.toJPEG(90);
// save file as testScaled.jpg, I used jetpack.writeAsync
});
});
The first two results, testDefault.jpg and testSetViewSize.jpg will be identical. They will be captureRect * scale factor in size but will only contain captureRect / scale factor from the webview.
The third result, testScaled.jpg, will contain the captureRect data from the webview but will be captureRect * scale factor * scale factor in size.
All images have upscaling artifacts in them.
Note
On non-retina displays (where the scale factor is 1), capturePage works as expected.
Also, using webview.capturePage or webview.getWebContents().capturePage makes no difference. It also does not matter if the code is executed in the main or renderer process. I wouldn’t expect there to be a difference in either, but I did test both to make sure.
I have a discussion started about this but decided to enter an issue here because I believe there is a bug in how the scale factor is used. https://discuss.atom.io/t/webview-capturepage-doesnt-capture-full-page-on-retina-display/37560
About this issue
- Original URL
- State: closed
- Created 8 years ago
- Reactions: 9
- Comments: 26 (3 by maintainers)
Hey guys, I apologize for the delayed response. So here is some code from our application that we are using to capture the page. This code is running on Windows 10, Linux Mint Debian Edition, non-retina Macs, and retina MacBooks. This is essentially the 3rd scenario from my original post on this issue - the root issue is still present, but can be managed. We have just accepted that the captured jpeg on retina displays will have a higher image resolution than expected but we have found that the quality of the image is acceptable. In our case, the application is being used for digital evidence preservation so we opted to keep the raw captured image. However, if you want the resulting image to be the expected dimensions you would resize it down by scaleFactor.
Please note that I ripped out the pertinent code from our application and had to sanitize it quite a bit as it is a closed source application. As such, this code as it’s copied here is not running exactly as is - it’s meant to just show what we’re doing.
The code is currently running against Electron v1.4.15 in production and v1.6.5 in dev.
I should have added that my method is a workaround - it’s not something that I think should be merged.
It seems the method
webContents.capturePageshould have some clarification since it seems most developers expect it to capture the full page length instead of what’s visible in the viewport.I have yet to try Puppeteer’s full-page screenshot method but this sounds like what developers are looking for in Electron:
page.screenshot([fullPage: true])I followed a couple of issues to this thread and the above, while helpful, didn’t necessarily solve the original issue that got me here:
I found a fix by opening an offscreen BrowserWindow and enabling the window to be larger than the screen through the following BrowserWindow([options]):
enableLargerThanScreen: trueExecute Javascript on the webContents to return the
document.body.offsetHeightBefore executing the capturePage method, resize the BrowserWindow with
win.setContentSize()using the page’s offsetHeight result.I haven’t used this in production but this is returning a complete webpage.
The issue still happens for me, but I have code implemented that works around the issue in a reliable manner - our application has been in production for a couple of months. I am tied up for most of the day but I will try and get sample working code posted for you guys this afternoon.
Note that in the code snippet that @mattgalvin provided there’s a chance that captureRect.width and captureRect.height become “non-integer” floats. If f.ex.
captureRect.widthis3120.5, then an exception may be raised because integers are expected.To fix this, I replaced
with
If someone would be interested in code of the workaround:
You can use a callback for the
paintevent:but I also do export to PDF and other formats and found it difficult to convert the callback to Promise-resolving (using Deferred maybe ?)
And no guarantee, that the
offscreenRendererwill be properly destroyed and the code does not cause any memory leaks. Correct me please if I am wrong.Our code also affected by this issue. Is someone currently working on this? any known solution? Thx