SVG визуализируется на холсте, размытым на сетчатке

У меня проблема с визуализацией SVG в canvas. На сетчатке отображается холст, отрисованный как base64 url ​​и установленный как SRC в размыто.

Я попробовал различные методы, которые были описаны в списке ниже, но не повезло:

Теперь я не знаю, что мне делать, чтобы сделать это лучше. Пожалуйста, посмотрите на мой результат: 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%, делают это по уважительной причине (со зрением) и не оценят, что вы обойдете их настройки.

Другие вопросы по тегам