在Docker上使用Phantomjs(或wkhtmltoimage)截屏时,角落模糊且渲染质量差。

13
我正在尝试从HTML生成图像(jpg或png),已经尝试过PhantomJS(通过php中的jonnyw / php-phantomjs)和wkhtmltoimage,但它们在生成图像时都有同样的问题。任何边框半径、图像或字体都有非常糟糕的锯齿边缘,不够清晰。一开始我以为是没有加载字体,但我的字体图标很好用,只是质量很差。我设置了100的质量,使用Phantomjs或wkhtmltoimage在任何网站上都得到相同的结果。

enter image description here

我想知道是什么原因导致了这个问题?

更新

enter image description here

更新2

这是从jonnyw/php-phantomjs使用的代码:

        $client = Client::getInstance();
        $client->isLazy();
        $client->getEngine()->setPath('phantomjs');
        $client->getEngine()->debug(true);

        $width  = 560;
        $height = 670;
        $top    = 1;
        $left   = 1;

        $request = $client->getMessageFactory()->createCaptureRequest('https://myurltoscreengrab.com', 'GET');
        $request->setOutputFile('uploads/stats/test.png');
        $request->setFormat('png');

        $request->setViewportSize($width, $height);
        $request->setCaptureDimensions($width, $height, $top, $left);

        $response = $client->getMessageFactory()->createResponse();

        // Send the request
        $client->send($request, $response);

正在使用JS

/**
 * Set up page and script parameters
 */
var page       = require('webpage').create(),
    system     = require('system'),
    response   = {},
    debug      = [],
    logs       = [],
    procedure  = {},
    resources  = 0,
    timeout;

/**
 * Global variables
 */


/**
 * Define width & height of capture
 */


var rectTop    = 1,
    rectLeft   = 1,
    rectWidth  = 530,
    rectHeight = 670;

if(rectWidth && rectHeight) {

    debug.push(new Date().toISOString().slice(0, -5) + ' [INFO] PhantomJS - Set capture clipping size ~ top: ' + rectTop + ' left: ' + rectLeft + ' ' + rectWidth + 'x' + rectHeight);

    page.clipRect = {
        top: rectTop,
        left: rectLeft,
        width: rectWidth,
        height: rectHeight
    };
}


/**
 * Define paper size.
 */


/**
 * Define viewport size.
 */

var viewportWidth  = 530,
    viewportHeight = 670;

if(viewportWidth && viewportHeight) {

    debug.push(new Date().toISOString().slice(0, -5) + ' [INFO] PhantomJS - Set viewport size ~ width: ' + viewportWidth + ' height: ' + viewportHeight);

    page.viewportSize = {
        width: viewportWidth,
        height: viewportHeight
    };
}




/**
 * Define custom headers.
 */

page.customHeaders = {};



/**
 * Page settings
 */

page.settings.resourceTimeout = 5000;



/**
 * On resource timeout
 */
page.onResourceTimeout = function (error) {

response        = error;
response.status = error.errorCode;


};

/**
 * On resource requested
 */
page.onResourceRequested = function (req) {





    resources++;
    window.clearTimeout(timeout);
};

/**
 * On resource received
 */
page.onResourceReceived = function (res) {

    var resource = res; // To be removed in version 5.0


if(!response.status) {
    response = resource;
}



    if(!res.stage || res.stage === 'end') {

        resources--;

        if (resources === 0) {

            timeout = window.setTimeout(function() {
                procedure.execute('success');
            }, 300);
        }
    }
};

/**
 * Handle page errors
 */
page.onError = function (msg, trace) {

var error = {
    message: msg,
    trace: []
};

trace.forEach(function(t) {
    error.trace.push(' -> ' + (t.file || t.sourceURL) + ': ' + t.line + (t.function ? ' (in function ' + t.function + ')' : ''));
});

logs.push(error);


};

/**
 * Handle global errors
 */
phantom.onError = function(msg, trace) {

var stack = [];

trace.forEach(function(t) {
    stack.push(' -> ' + (t.file || t.sourceURL) + ': ' + t.line + (t.function ? ' (in function ' + t.function + ')' : ''));
});

response.status  = 500;
response.content = msg;
response.console = stack;

system.stdout.write(JSON.stringify(response, undefined, 4));
phantom.exit(1);


};

/**
 * Open page
 */
page.open ('https://boxstat.co/widgets/image/stats/2898784/18/500/FFFFFF-EEEEEE-fafafa-333333-85bd4d-ffffff-e4f8cf-71b42f-fddfc1-bd6610-fad3c9-c85639-fac9c9-c52e2e', 'GET', '', function (status) {


page.evaluate(function() {

    var styles = {};

    for(var property in styles) {
        document.body.style[property] = styles[property];
    }
});

    window.setTimeout(function () { 
        procedure.execute(status); 
    }, 4800);
});

/**
 * Execute procedure
 */
procedure.execute = function (status) {

if (status === 'success') {

    try {

        page.render('uploads/stats/test.png', {
            format: 'png',
            quality: 100,
        });

        response.content = page.evaluate(function () {
            return document.getElementsByTagName('html')[0].innerHTML
        });

    } catch(e) {

        response.status  = 500;
        response.content = e.message;
    }
}

response.console = logs;

system.stderr.write(debug.join('\\n') + '\\n');
system.stdout.write(JSON.stringify(response, undefined, 4));

phantom.exit();

};

当我将其渲染为JPEG格式时,质量是100。在默认设置下,即使是纯色也看起来很糟糕。我已经在上面的描述中添加了一个新的屏幕截图,质量为75。 - john
1
请添加您的PhantomJS代码! - Oleksii Dniprovskyi
@AlexDnepr 我已经添加了我在PHP中使用的代码。据我所见,这使用了bundle和命令行界面。但是由于wkhtmltoimage也出现了相同的问题,我认为这可能与Phantomjs没有直接关系? - john
感谢@AlexDnepr,质量函数似乎出于某种原因不影响PNG文件,但现在仍在使用。宽度和高度的更改并没有使情况变得更好,它们只是改变了被裁剪的区域。如果有帮助的话,我已经将正在呈现的JS添加到我的问题中了。 - john
其他格式都能正常工作吗?顺便说一下,你的JS代码看起来不错!很抱歉现在不能告诉你更多。如果发现什么问题,我会通知你的! - Oleksii Dniprovskyi
显示剩余3条评论
1个回答

1
在你的phantomjs中添加viewportSize和zoomFactor,如下所示:
await page.property('viewportSize', { height: 1600, width: 3600 });
await page.property('zoomFactor', 4);

并/或添加:

<script>
  window.devicePixelRatio = 4;
</script>

尝试使用与屏幕DPI相关的更高纸张DPI设置缩放因子:
page.zoomFactor = 300 / 96;   // or use / 72

在定义页面大小后必须设置。

我还发现了两个尝试解决这种问题的函数...

函数1

var makeHighResScreenshot = function(srcEl, destIMG, dpi) {
    var scaleFactor = Math.floor(dpi / 96);
    // Save original size of element
    var originalWidth = srcEl.offsetWidth;
    var originalHeight = srcEl.offsetHeight;
    // Save original document size
    var originalBodyWidth = document.body.offsetWidth;
    var originalBodyHeight = document.body.offsetHeight;

    // Add style: transform: scale() to srcEl
    srcEl.style.transform = "scale(" + scaleFactor + ", " + scaleFactor + ")";
    srcEl.style.transformOrigin = "left top";

    // create wrapper for srcEl to add hardcoded height/width
    var srcElWrapper = document.createElement('div');
    srcElWrapper.id = srcEl.id + '-wrapper';
    srcElWrapper.style.height = originalHeight*scaleFactor + 'px';
    srcElWrapper.style.width = originalWidth*scaleFactor + 'px';
    // insert wrapper before srcEl in the DOM tree
    srcEl.parentNode.insertBefore(srcElWrapper, srcEl);
    // move srcEl into wrapper
    srcElWrapper.appendChild(srcEl);

    // Temporarily remove height/width constraints as necessary
    document.body.style.width = originalBodyWidth*scaleFactor +"px";
    document.body.style.height = originalBodyHeight*scaleFactor +"px";

    window.scrollTo(0, 0); // html2canvas breaks when we're not at the top of the doc, see html2canvas#820
    html2canvas(srcElWrapper, {
        onrendered: function(canvas) {
            destIMG.src = canvas.toDataURL("image/png");
            srcElWrapper.style.display = "none";
            // Reset height/width constraints
            document.body.style.width = originalBodyWidth + "px";
            document.body.style.height = originalBodyHeight + "px";
        }
    });
};

使用方法

var src = document.getElementById("screenshot-source");
var img = document.getElementById("screenshot-img");
makeHighResScreenshot(src, img, 192); // DPI of 192 is 4x resolution (2x normal DPI for both width and height)

功能 2
function takeHighResScreenshot(srcEl, destIMG, scaleFactor) {
    // Save original size of element
    var originalWidth = srcEl.offsetWidth;
    var originalHeight = srcEl.offsetHeight;
    // Force px size (no %, EMs, etc)
    srcEl.style.width = originalWidth + "px";
    srcEl.style.height = originalHeight + "px";

    // Position the element at the top left of the document because of bugs in html2canvas. The bug exists when supplying a custom canvas, and offsets the rendering on the custom canvas based on the offset of the source element on the page; thus the source element MUST be at 0, 0.
    // See html2canvas issues #790, #820, #893, #922
    srcEl.style.position = "absolute";
    srcEl.style.top = "0";
    srcEl.style.left = "0";

    // Create scaled canvas
    var scaledCanvas = document.createElement("canvas");
    scaledCanvas.width = originalWidth * scaleFactor;
    scaledCanvas.height = originalHeight * scaleFactor;
    scaledCanvas.style.width = originalWidth + "px";
    scaledCanvas.style.height = originalHeight + "px";
    var scaledContext = scaledCanvas.getContext("2d");
    scaledContext.scale(scaleFactor, scaleFactor);

    html2canvas(srcEl, { canvas: scaledCanvas })
    .then(function(canvas) {
        destIMG.src = canvas.toDataURL("image/png");
        srcEl.style.display = "none";
    });
};

使用方法

var src = document.getElementById("screenshot-src");
var img = document.getElementById("screenshot-img");
takeHighResScreenshot(src, img, 2); // This time we provide desired scale factor directly, no more messing with DPI

我希望这可以帮助你。现在让我告诉你我会做什么。我已经制作了一段时间的浏览器自动化脚本,PhantomJS 在我看来并不是很好。考虑使用 NightmareJS。它比 Phantom 更快,更易于使用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接