Я пытаюсь сгенерировать изображения (jpg или png) из HTML, и я уже пробовал PhantomJS (через jonnyw / php-phantomjs в php) и wkhtmltoimage, но у них обоих одна и та же проблема при создании изображения. Любой радиус границы, изображения или шрифты имеют действительно плохие неровные края и совсем не четкие.
Сначала я думал, что это не загружаемые шрифты, но мои иконки шрифтов работают нормально, они просто очень низкого качества. У меня установлено 100 качественных показателей, и я получаю те же результаты при использовании Phantomjs или wkhtmltoimage на любом веб-сайте.
Кто-нибудь знает, что может быть причиной этого?
ОБНОВИТЬ
ОБНОВЛЕНИЕ 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.0if(!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();
};
Добавьте 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
Должен быть установлен после определения размера страницы.
Я также нашел 2 функции, которые пытаются решить эту проблему …
Функция 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, IMO, не очень хорош. Подумайте об использовании NightmareJS. Это НАМНОГО быстрее, чем Призрак и проще в использовании.
Других решений пока нет …