2013-10-02 5 views
8

Próbuję narysować obraz 300dpi do obiektu canvas, ale w Chrome jest on wyświetlany w bardzo złej jakości. Kiedy użyłem poniższego kodu, to się nie poprawiło, ale to dlatego, że devicePixelRatio było takie samo jak backingStoreRatio (oba były 1).Jak narysować w Chrome wysoką rozdzielczość na płótnie? I dlaczego, jeśli devicePixelRatio === webkitBackingStorePixelRatio skaluje się, aby 2x poprawić rozdzielczość?

Potem próbował wymusić pewne zmiany przełożenia i stwierdził, co następuje:

  • gdybym zmienił ratio być 2 i zmusił kod skalowania do uruchomienia, a następnie rysuje na płótnie w lepszej rozdzielczości.
  • Gdybym zmienił ratio do niczego większy niż 2 (np 3, 4, 5, 6, etc), a następnie miał słabą rozdzielczość!

Wszystko to zostało wykonane na komputerze stacjonarnym.

Jak zapewnić wydruk na płótnie o wysokiej rozdzielczości?

(Kod z: http://www.html5rocks.com/en/tutorials/canvas/hidpi/)

/** 
* Writes an image into a canvas taking into 
* account the backing store pixel ratio and 
* the device pixel ratio. 
* 
* @author Paul Lewis 
* @param {Object} opts The params for drawing an image to the canvas 
*/ 
function drawImage(opts) { 

    if(!opts.canvas) { 
     throw("A canvas is required"); 
    } 
    if(!opts.image) { 
     throw("Image is required"); 
    } 

    // get the canvas and context 
    var canvas = opts.canvas, 
    context = canvas.getContext('2d'), 
    image = opts.image, 

    // now default all the dimension info 
    srcx = opts.srcx || 0, 
    srcy = opts.srcy || 0, 
    srcw = opts.srcw || image.naturalWidth, 
    srch = opts.srch || image.naturalHeight, 
    desx = opts.desx || srcx, 
    desy = opts.desy || srcy, 
    desw = opts.desw || srcw, 
    desh = opts.desh || srch, 
    auto = opts.auto, 

    // finally query the various pixel ratios 
    devicePixelRatio = window.devicePixelRatio || 1, 
    backingStoreRatio = context.webkitBackingStorePixelRatio || 
    context.mozBackingStorePixelRatio || 
    context.msBackingStorePixelRatio || 
    context.oBackingStorePixelRatio || 
    context.backingStorePixelRatio || 1,  
    ratio = devicePixelRatio/backingStoreRatio; 

    // ensure we have a value set for auto. 
    // If auto is set to false then we 
    // will simply not upscale the canvas 
    // and the default behaviour will be maintained 
    if (typeof auto === 'undefined') { 
     auto = true; 
    } 

    // upscale the canvas if the two ratios don't match 
    if (auto && devicePixelRatio !== backingStoreRatio) { 

     var oldWidth = canvas.width; 
     var oldHeight = canvas.height; 

     canvas.width = oldWidth * ratio; 
     canvas.height = oldHeight * ratio; 

     canvas.style.width = oldWidth + 'px'; 
     canvas.style.height = oldHeight + 'px'; 

     // now scale the context to counter 
     // the fact that we've manually scaled 
     // our canvas element 
     context.scale(ratio, ratio); 

    } 

    context.drawImage(pic, srcx, srcy, srcw, srch, desx, desy, desw, desh); 
} 

Making wyniki tylko poniższe zmiany w obrazach na płótnie wysokiej rozdzielczości (dlaczego?):

//WE FORCE RATIO TO BE 2 
    ratio = 2; 

    //WE FORCE IT TO UPSCALE (event though they're equal) 
    if (auto && devicePixelRatio === backingStoreRatio) { 

przypadku zmiany powyższych być stosunek 3, to już nie jest wysoka rozdzielczość!

EDIT: Jedna dodatkowa obserwacja - nawet ze stosunkiem 2x, gdy jest zauważalnie lepsza rozdzielczość, to jeszcze nie tak ostre jak pokazano obraz w tagu img)

+0

Zobacz to: http://stackoverflow.com/a/15666143/3655 – Aardvark

+0

@Aardvark Jak wspomina w moim poście , Próbowałem już kodu z tego artykułu (HTML5Rocks), który jest tym, co ten wpis odwołuje, i to nie działa, ponieważ zawsze pokazuje stosunek jako '1', który nic nie robi. –

+0

Czy pomieszałeś z imageSmoothingEnabled? – Aardvark

Odpowiedz

3

Jeden trick można użyć jest w rzeczywistości ustaw szerokość i wysokość płótna na wysokość i szerokość piksela zdjęcia, a następnie użyj css, aby zmienić rozmiar obrazu. Poniżej znajduje się przykład sudo-code.

canvasElement.width = imgWidth; 
canvasElement.height = imgHeight; 
canvasElement.getContext("2d").drawImage(ARGS); 

Następnie można użyć szerokość ustawienie & wysokość lub 3D Transform.

canvasElement.style.transform = "scale3d(0.5,0.5,0)"; 

lub

canvasElement.style.width = newWidth; 
canvasElement.style.height = newWidth * imgHeight/imgWidth; 

Proponuję użyć 3D przekształcić ponieważ podczas korzystania z 3D przekształca dane obrazu elementu zostanie skopiowany do GPU tak naprawdę nie trzeba się martwić o każdy pogorszenie jakości.

Mam nadzieję, że to pomoże!

9

Artykuł HTML5Rocks związane z pytaniem sprawia, że ​​rzeczy trudniejsze niż muszą być, ale to sprawia, że ​​ten sam podstawowy błąd, co innych zasobów widziałem (1, 2, 3, 4).Te odniesienia dają pewną odmianę tego wzoru:

var rect = canvas.getBoundingClientRect(); 
canvas.width = Math.round (devicePixelRatio * rect.width); // WRONG! 

Formuła jest błędna. Lepsze formuła

var rect = canvas.getBoundingClientRect(); 
canvas.width = Math.round (devicePixelRatio * rect.right) 
      - Math.round (devicePixelRatio * rect.left); 

Chodzi o to, że nie ma sensu skali szerokość lub wysokość (tj różnica dwóch pozycjach) przez devicePixelRatio. Powinieneś zawsze skalować bezwzględną pozycję. Nie mogę znaleźć odniesienia do tego dokładnego punktu, ale myślę, że jest to oczywiste, kiedy już je otrzymasz.

Propozycja.

Nie ma możliwości obliczenia fizycznej szerokości i wysokości prostokąta (w pikselach urządzenia) z jego szerokości i wysokości (w pikselach niezależnych od urządzenia).

Dowód.

Załóżmy, że masz dwa elementy, których ograniczający prostokąty urządzeniu niezależne pikseli są

{ left: 0, top: 10, right: 8, bottom: 20, width: 8, height: 10 }, 
{ left: 1, top: 20, right: 9, bottom: 30, width: 8, height: 10 }. 

Teraz zakładając devicePixeRatio 1.4 elementy obejmie te prostokąty device-pikselowe:

{ left: 0, top: 14, right: 11, bottom: 28, width: 11, height: 14 }, 
{ left: 1, top: 28, right: 13, bottom: 42, width: 12, height: 14 }, 

gdzie w lewo, górny, prawy i dolny zostały pomnożone przez urządzeniePixelRatio i zaokrąglone do najbliższej liczby całkowitej (za pomocą Math.round()).

Zauważysz, że dwa prostokąty mają tę samą szerokość w pikselach niezależnych od urządzenia, ale o różnych szerokościach w pikselach urządzenia. ▯

Testowanie.

Oto przykład kodu do testowania. Załaduj go do przeglądarki, a następnie powiększ i pomniejsz za pomocą myszy. Ostatnie płótno zawsze powinno mieć ostre linie. Pozostałe trzy będą rozmyte w niektórych rozdzielczościach.

Testowane na komputerach Firefox, IE, Edge, Chrome i Android Chrome i Firefox. (Uwaga, to nie działa na JSfiddle ponieważ getBoundingClientRect zwraca niepoprawne wartości istnieje.)

<!DOCTYPE html> 
<html> 
    <head> 
    <script> 
     function resize() { 
     var canvases = document.getElementsByTagName("canvas"); 
     var i, j; 
     for (i = 0; i != canvases.length; ++ i) { 
      var canvas = canvases[i]; 
      var method = canvas.getAttribute("method"); 
      var dipRect = canvas.getBoundingClientRect(); 
      var context = canvas.getContext("2d"); 
      switch (method) { 
      case "0": 
       // Incorrect: 
       canvas.width = devicePixelRatio * dipRect.width; 
       canvas.height = devicePixelRatio * dipRect.height; 
       break; 

      case "1": 
       // Incorrect: 
       canvas.width = Math.round(devicePixelRatio * dipRect.width); 
       canvas.height = Math.round(devicePixelRatio * dipRect.height); 
       break; 

      case "2": 
       // Incorrect: 
       canvas.width = Math.floor(devicePixelRatio * dipRect.width); 
       canvas.height = Math.floor(devicePixelRatio * dipRect.height); 
       break; 

      case "3": 
       // Correct: 
       canvas.width = Math.round(devicePixelRatio * dipRect.right) 
       - Math.round(devicePixelRatio * dipRect.left); 
       canvas.height = Math.round(devicePixelRatio * dipRect.bottom) 
       - Math.round(devicePixelRatio * dipRect.top); 
       break; 
      } 
      console.log("method " + method 
      + ", devicePixelRatio " + devicePixelRatio 
      + ", client rect (DI px) (" + dipRect.left + ", " + dipRect.top + ")" 
      + ", " + dipRect.width + " x " + dipRect.height 
      + ", canvas width, height (logical px) " + canvas.width + ", " + canvas.height); 

      context.clearRect(0, 0, canvas.width, canvas.height); 
      context.fillStyle = "cyan"; 
      context.fillRect(0, 0, canvas.width, canvas.height); 
      context.fillStyle = "black"; 
      for (j = 0; j != Math.floor (canvas.width/2); ++ j) { 
      context.fillRect(2 * j, 0, 1, canvas.height); 
      } 
     } 
     }; 
     addEventListener("DOMContentLoaded", resize); 
     addEventListener("resize", resize); 
    </script> 
    </head> 
    <body> 
    <canvas method="0" style="position: absolute; left: 1px; top: 10px; width: 8px; height: 10px"></canvas> 
    <canvas method="1" style="position: absolute; left: 1px; top: 25px; width: 8px; height: 10px"></canvas> 
    <canvas method="2" style="position: absolute; left: 1px; top: 40px; width: 8px; height: 10px"></canvas> 
    <canvas method="3" style="position: absolute; left: 1px; top: 55px; width: 8px; height: 10px"></canvas> 
    </body> 
</html> 
Powiązane problemy