SVG визуализируется на холсте, размытым на сетчатке
У меня проблема с визуализацией SVG в canvas. На сетчатке отображается холст, отрисованный как base64 url и установленный как SRC в размыто.
Я попробовал различные методы, которые были описаны в списке ниже, но не повезло:
- https://tristandunn.com/2014/01/24/rendering-svg-on-canvas/
- Как исправить размытый текст на холсте HTML5?
- Рисование холста и показ Retina: выполнимо?
- https://www.html5rocks.com/en/tutorials/canvas/hidpi/
Теперь я не знаю, что мне делать, чтобы сделать это лучше. Пожалуйста, посмотрите на мой результат: jsfiddle.net/a8bj5fgj/2/
Редактировать:
Обновленная скрипка с исправлением: jsfiddle.net/a8bj5fgj/7/
1 ответ
Retina Display
Дисплеи Retina и с очень высоким разрешением имеют размеры в пикселях меньше, чем может разрешить средний человеческий глаз. Рендеринг одной линии в конечном итоге выглядит как более легкая линия. Чтобы устранить проблемы, связанные с отображением страниц с высоким разрешением, размер пикселя CSS по умолчанию изменится на 2.
DOM знает об этом и корректирует свой рендеринг для компенсации. Но Canvas не знает, и его рендеринг только увеличивается. Тип отображения по умолчанию для холста - билинейная интерполяция. Это сглаживает переход от одного пикселя к другому, что отлично подходит для фотографий, но не так хорошо для линий, текста, SVG и тому подобного.
Некоторые решения
Сначала стоит включить билинейную фильтрацию на холсте. Это можно сделать с помощью правила CSS
image-rendering: pixelated;
Хотя это не создаст качество SVG, отображаемого на DOM, это уменьшит размытость, которую испытывают некоторые пользователи.При рендеринге SVG на холст вы должны отключить сглаживание изображения, так как это может снизить качество изображения SVG. SVG визуализируется внутри и не требует дополнительного сглаживания, когда внутренняя копия отображается на холсте.
Сделать это
ctx.imageSmoothingEnabled = false;
Определить размер пикселя CSS. Переменная окна
devicePixelRatio
возвращает размер пикселя CSS по сравнению с фактическим размером физического пикселя экрана. Устройства Retina и High res обычно имеют значение 2. Затем вы можете использовать его для установки разрешения холста, соответствующего физическому разрешению в пикселях.Но есть проблема, потому что
devicePixelRatio
не поддерживается во всех браузерах иdevicePixelRatio
зависит от настройки масштабирования страницы.Так что при самом основном использовании
devicePixelRatio
и предположение, что немногие люди приближаются к 200%.
Код Предполагая, что canvas.style.width
а также canvas.style.height
уже правильно установлены.
if(devicePixelRatio >= 2){
canvas.width *= 2;
canvas.height *= 2;
}
Теперь, когда вы увеличили разрешение, вы также должны увеличить размер рендеринга. Это можно сделать с помощью преобразования canvas, а еще лучше создать его как функцию.
function setCanvasForRetina(canvas){
canvas.width *= 2;
canvas.height *= 2;
canvas.setTransform(2,0,0,2,0,0);
}
Примечание. Я не увеличиваю размер пикселя значением "devicePixelRatio". Это связано с тем, что устройства с сетчаткой будут иметь разрешение только в 2 раза, а если соотношение сторон больше 2, это происходит из-за увеличения масштаба клиента. Поведение холста я не подстраиваю под настройку масштабирования, если могу. Хотя это не правило, а просто предложение.
Лучше угадай
Два вышеупомянутых метода - или решение остановки разрыва или простое предположение. Вы можете улучшить свои шансы, изучив некоторые системы.
Дисплеи Retina в настоящее время имеют фиксированный набор разрешений для фиксированного набора устройств (телефонов, планшетов, ноутбуков).
Вы можете запросить window.screen.width
а также window.screen.height
определить абсолютное физическое разрешение в пикселях и сопоставить его с известным разрешением экрана. Вы также можете запросить userAgent, чтобы определить тип устройства и марку.
Собрав все воедино, вы можете улучшить предположение. Следующая функция делает предположение, если дисплей сетчатки. Вы можете использовать нечто подобное, чтобы определить, является ли устройство сетчаткой, а затем соответственно увеличить разрешение холста.
Информацию для следующего кода можно найти на вики-моделях Retina Display. Эта информация может быть запрошена машиной с использованием интерфейса SPARQL Wiki, если вы хотите поддерживать ее в актуальном состоянии.
Демо-версия "Угадай, если сетчатка".
rWidth.textContent = screen.width
rHeight.textContent = screen.height
aWidth.textContent = screen.availWidth
aHeight.textContent = screen.availHeight
pWidth.textContent = innerWidth
pHeight.textContent = innerHeight
dWidth.textContent = document.body.clientWidth
dHeight.textContent = document.body.clientHeight
//doWidth.textContent = document.body.offsetWidth
//doHeight.textContent = document.body.offsetHeight
//sWidth.textContent = document.body.scrollWidth
//sHeight.textContent = document.body.scrollHeight
pAspect.textContent = devicePixelRatio
userA.textContent = navigator.userAgent
function isRetina(){
// source https://en.wikipedia.org/wiki/Retina_Display#Models
var knownRetinaResolutions = [[272,340], [312,390], [960,640], [1136,640 ], [1334,750 ], [1920,1080], [2048,1536], [2732,2048], [2304,1440], [2560,1600], [2880,1800], [4096,2304], [5120,2880]];
var knownPhones = [[960,640], [1136,640 ], [1334,750 ], [1920,1080]];
var knownPads = [[2048,1536], [2732,2048]];
var knownBooks = [[2304,1440], [2560,1600], [2880,1800], [4096,2304], [5120,2880]];
var hasRetinaRes = knownRetinaResolutions.some(known => known[0] === screen.width && known[1] === screen.height);
var isACrapple = /(iPhone|iPad|iPod|Mac OS X|MacPPC|MacIntel|Mac_PowerPC|Macintosh)/.test(navigator.userAgent);
var hasPhoneRes = knownPhones.some(known => known[0] === screen.width && known[1] === screen.height);
var isPhone = /iPhone/.test(navigator.userAgent);
var hasPadRes = knownPads.some(known => known[0] === screen.width && known[1] === screen.height);
var isPad = /iPad/.test(navigator.userAgent);
var hasBookRes = knownBooks.some(known => known[0] === screen.width && known[1] === screen.height);
var isBook = /Mac OS X|MacPPC|MacIntel|Mac_PowerPC|Macintosh/.test(navigator.userAgent);
var isAgentMatchingRes = (isBook && hasBookRes && !isPad && !isPhone) ||
(isPad && hasPadRes && !isBook && !isPhone) ||
(isPhone && hasPhoneRes && !isBook && !isPad)
return devicePixelRatio >= 2 &&
isACrapple &&
hasRetinaRes &&
isAgentMatchingRes;
}
guess.textContent = isRetina() ? "Yes" : "No";
div, h1, span {
font-family : arial;
}
span {
font-weight : bold
}
<div class="r-display" id="info">
<h1>System info</h1>
<div>Device resolution :
<span id = "rWidth"></span> by <span id = "rHeight"></span> pixels
</div>
<div>Availabe resolution :
<span id = "aWidth"></span> by <span id = "aHeight"></span> pixels
</div>
<div>Page resolution :
<span id = "pWidth"></span> by <span id = "pHeight"> </span> CSS pixels
</div>
<div>Document client res :
<span id = "dWidth"></span> by <span id = "dHeight"> </span> CSS pixels
</div>
<div>Pixel aspect :
<span id = "pAspect"></span>
</div>
<div>User agent :
<span id="userA"></span>
</div>
<h3>Best guess is retina "<span id = "guess"></span>!"</h3>
</div>
Из вашего фрагмента
Это может делать то, что вы хотите. Так как я не владею никакими яблочными продуктами, я не могу протестировать их, кроме того, что заставляю true на isRetina.
function isRetina() {
// source https://en.wikipedia.org/wiki/Retina_Display#Models
var knownRetinaResolutions = [[272, 340], [312, 390], [960, 640], [1136, 640], [1334, 750], [1920, 1080], [2048, 1536], [2732, 2048], [2304, 1440], [2560, 1600], [2880, 1800], [4096, 2304], [5120, 2880]];
var knownPhones = [[960, 640], [1136, 640], [1334, 750], [1920, 1080]];
var knownPads = [[2048, 1536], [2732, 2048]];
var knownBooks = [[2304, 1440], [2560, 1600], [2880, 1800], [4096, 2304], [5120, 2880]];
var hasRetinaRes = knownRetinaResolutions.some(known => known[0] === screen.width && known[1] === screen.height);
var isACrapple = /(iPhone|iPad|iPod|Mac OS X|MacPPC|MacIntel|Mac_PowerPC|Macintosh)/.test(navigator.userAgent);
var hasPhoneRes = knownPhones.some(known => known[0] === screen.width && known[1] === screen.height);
var isPhone = /iPhone/.test(navigator.userAgent);
var hasPadRes = knownPads.some(known => known[0] === screen.width && known[1] === screen.height);
var isPad = /iPad/.test(navigator.userAgent);
var hasBookRes = knownBooks.some(known => known[0] === screen.width && known[1] === screen.height);
var isBook = /Mac OS X|MacPPC|MacIntel|Mac_PowerPC|Macintosh/.test(navigator.userAgent);
var isAgentMatchingRes = (isBook && hasBookRes && !isPad && !isPhone) ||
(isPad && hasPadRes && !isBook && !isPhone) ||
(isPhone && hasPhoneRes && !isBook && !isPad);
return devicePixelRatio >= 2 && isACrapple && hasRetinaRes && isAgentMatchingRes;
}
function svgToImage(svg){
function svgAsImg() {
var canvas, ctx;
canvas = document.createElement("canvas");
ctx = canvas.getContext("2d");
var width = this.width;
var height = this.height;
var scale = 1;
if(isRetina()){
width *= 2;
height *= 2;
scale = 2;
}
canvas.width = width;
canvas.height = height;
ctx.setTransform(scale, 0, 0, scale, 0, 0);
ctx.imageSmoothingEnabled = false; // SVG rendering is better with smoothing off
ctx.drawImage(this,0,0);
DOMURL.revokeObjectURL(url);
try{
var image = new Image();
image.src = canvas.toDataURL();
imageContainer.appendChild(image);
image.width = this.width;
image.height = this.height;
}catch(e){ // in case of CORS error fallback to canvas
canvas.style.width = this.width + "px"; // in CSS pixels not physical pixels
canvas.style.height = this.height + "px";
imageContainer.appendChild(canvas); // just use the canvas as it is an image as well
}
};
var url;
var img = new Image();
var DOMURL = window.URL || window.webkitURL || window;
img.src = url = DOMURL.createObjectURL(new Blob([svg], {type: 'image/svg+xml'}));
img.onload = svgAsImg;
}
svgToImage(svgContainer.innerHTML);
<div id="svgContainer"><svg width="31" height="40" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 43 55" fill="#736b9e"><path d="m 40.713968,30.966202 c 0.0028,0.05559 -0.01078,0.114956 -0.044,0.178882 -1.545645,2.974287 -2.853499,5.591663 -4.339695,7.673668 -0.788573,1.104704 -2.095869,2.778673 -2.874223,3.773068 -0.994236,1.02684 -6.879641,7.657944 -6.167884,7.049648 -1.292899,1.235403 -5.717368,5.476022 -5.717368,5.476022 0,0 -4.323294,-3.985179 -5.928388,-5.591297 C 14.037321,47.920078 10.708239,43.994015 9.6976253,42.770306 8.6870114,41.546601 8.5086687,40.900753 6.8441265,38.818752 5.8958518,37.63265 4.1376268,34.24638 3.0745121,32.156026 2.9037625,31.86435 2.7398218,31.568267 2.5826899,31.268005 2.5509386,31.228498 2.5238331,31.18779 2.5044312,31.145084 2.4575955,31.041974 2.4164305,30.951055 2.3805569,30.87146 0.95511134,28.003558 0.15221914,24.771643 0.15221914,21.351725 c 0,-11.829154 9.58943056,-21.41858234 21.41858286,-21.41858234 11.829152,0 21.418583,9.58942834 21.418583,21.41858234 0,3.457576 -0.820406,6.72314 -2.275417,9.614477 z M 21.52596,1.5031489 c -10.866018,0 -19.6746717,8.8086521 -19.6746717,19.6746741 0,10.866016 8.8086537,19.674669 19.6746717,19.674669 10.866018,0 19.674672,-8.808648 19.674672,-19.674669 0,-10.866022 -8.808654,-19.6746741 -19.674672,-19.6746741 z" /><g transform="translate(6.5,6) scale(0.060546875)"><path d="M32 384h272v32H32zM400 384h80v32h-80zM384 447.5c0 17.949-14.327 32.5-32 32.5-17.673 0-32-14.551-32-32.5v-95c0-17.949 14.327-32.5 32-32.5 17.673 0 32 14.551 32 32.5v95z"></path><g><path d="M32 240h80v32H32zM208 240h272v32H208zM192 303.5c0 17.949-14.327 32.5-32 32.5-17.673 0-32-14.551-32-32.5v-95c0-17.949 14.327-32.5 32-32.5 17.673 0 32 14.551 32 32.5v95z"></path></g><g><path d="M32 96h272v32H32zM400 96h80v32h-80zM384 159.5c0 17.949-14.327 32.5-32 32.5-17.673 0-32-14.551-32-32.5v-95c0-17.949 14.327-32.5 32-32.5 17.673 0 32 14.551 32 32.5v95z"></path></g></g></svg>
</div>
<div id="imageContainer"></div>
Пожалуйста, обратите внимание.
Большинству людей, которые имеют зрение 6/6 (20/20 для имперских стран), будет трудно понять разницу между слегка размытым отображением холста и четким DOM. Вы должны спросить себя, нужно ли вам присмотреться, чтобы убедиться? Вы можете увидеть размытие на нормальном расстоянии просмотра?
Кроме того, некоторые люди, которые увеличили масштаб дисплея до 200%, делают это по уважительной причине (со зрением) и не оценят, что вы обойдете их настройки.