Jak naprawić rozmyty tekst na płótnie HTML5?

90

Jestem w sumie n00b z HTML5i pracuję z canvasrenderowanie kształty, kolory i tekst. W mojej aplikacji mam adapter widoku, który dynamicznie tworzy kanwę i wypełnia ją zawartością. Działa to naprawdę fajnie, poza tym, że mój tekst jest bardzo rozmyty / rozmyty / rozciągnięty. Widziałem wiele innych postów wyjaśniających, dlaczego zdefiniowanie szerokości i wysokości w programie CSSspowoduje ten problem, ale definiuję to wszystko w javascript.

Odpowiedni kod (zobacz Fiddle ):

var width  = 500;//FIXME:size.w;
var height = 500;//FIXME:size.h;
    
var canvas = document.createElement("canvas");
//canvas.className="singleUserCanvas";
canvas.width=width;
canvas.height=height;
canvas.border = "3px solid #999999";
canvas.bgcolor = "#999999";
canvas.margin = "(0, 2%, 0, 2%)";
    
var context = canvas.getContext("2d");

//////////////////
////  SHAPES  ////
//////////////////
    
var left = 0;

//draw zone 1 rect
context.fillStyle = "#8bacbe";
context.fillRect(0, (canvas.height*5/6)+1, canvas.width*1.5/8.5, canvas.height*1/6);

left = left + canvas.width*1.5/8.5;

//draw zone 2 rect
context.fillStyle = "#ffe381";
context.fillRect(left+1, (canvas.height*5/6)+1, canvas.width*2.75/8.5, canvas.height*1/6);

left = left + canvas.width*2.75/8.5 + 1;

//draw zone 3 rect
context.fillStyle = "#fbbd36";
context.fillRect(left+1, (canvas.height*5/6)+1, canvas.width*1.25/8.5, canvas.height*1/6);

left = left + canvas.width*1.25/8.5;

//draw target zone rect
context.fillStyle = "#004880";
context.fillRect(left+1, (canvas.height*5/6)+1, canvas.width*0.25/8.5, canvas.height*1/6);

left = left + canvas.width*0.25/8.5;
    
//draw zone 4 rect
context.fillStyle = "#f8961d";
context.fillRect(left+1, (canvas.height*5/6)+1, canvas.width*1.25/8.5, canvas.height*1/6);

left = left + canvas.width*1.25/8.5 + 1;

//draw zone 5 rect
context.fillStyle = "#8a1002";
context.fillRect(left+1, (canvas.height*5/6)+1, canvas.width-left, canvas.height*1/6);

////////////////
////  TEXT  ////
////////////////

//user name
context.fillStyle = "black";
context.font = "bold 18px sans-serif";
context.textAlign = 'right';
context.fillText("User Name", canvas.width, canvas.height*.05);

//AT:
context.font = "bold 12px sans-serif";
context.fillText("AT: 140", canvas.width, canvas.height*.1);

//AB:
context.fillText("AB: 94", canvas.width, canvas.height*.15);
       
//this part is done after the callback from the view adapter, but is relevant here to add the view back into the layout.
var parent = document.getElementById("layout-content");
parent.appendChild(canvas);
<div id="layout-content"></div>

Wyniki, które widzę (w Safari ) są znacznie bardziej wypaczone niż pokazane na skrzypcach:

Mój

Renderowane dane wyjściowe w Safari

Skrzypce

Renderowane dane wyjściowe na JSFiddle

Co robię niepoprawnie? Czy potrzebuję osobnego płótna dla każdego elementu tekstowego? Czy to czcionka? Czy muszę najpierw zdefiniować płótno w układzie HTML5? Czy jest literówka? Zgubiłem się.

Phil
źródło
Wygląda na to, że nie dzwonisz clearRect.
David
1
Ten polyfill naprawia większość podstawowych operacji kanwy w przeglądarkach HiDPI, które nie są automatycznie skalowane w górę (obecnie safari jest jedynym) ... github.com/jondavidjohn/hidpi-canvas-polyfill
jondavidjohn
Pracowałem nad frameworkiem JS, który rozwiązuje problem rozmycia płótna za pomocą mozaiki DIV. Tworzę
Gonki

Odpowiedzi:

161

Element canvas działa niezależnie od współczynnika pikseli urządzenia lub monitora.

Na iPadzie 3+ współczynnik ten wynosi 2. Oznacza to, że obszar roboczy o szerokości 1000 pikseli musiałby teraz wypełnić 2000 pikseli, aby dopasować się do szerokości określonej na wyświetlaczu iPada. Na szczęście dla nas odbywa się to automatycznie przez przeglądarkę. Z drugiej strony jest to również powód, dla którego widać mniej definicji na obrazach i elementach płótna, które zostały wykonane tak, aby bezpośrednio pasowały do ​​ich widocznego obszaru. Ponieważ Twoje płótno wie, jak wypełnić tylko 1000 pikseli, ale jest proszone o narysowanie do 2000 pikseli, przeglądarka musi teraz inteligentnie wypełnić puste miejsca między pikselami, aby wyświetlić element we właściwym rozmiarze.

Gorąco polecam przeczytanie tego artykułu z HTML5Rocks, który bardziej szczegółowo wyjaśnia, jak tworzyć elementy wysokiej rozdzielczości.

tl; dr? Oto przykład (oparty na powyższym tut), którego używam w moich własnych projektach, aby wypluć płótno o odpowiedniej rozdzielczości:

var PIXEL_RATIO = (function () {
    var ctx = document.createElement("canvas").getContext("2d"),
        dpr = window.devicePixelRatio || 1,
        bsr = ctx.webkitBackingStorePixelRatio ||
              ctx.mozBackingStorePixelRatio ||
              ctx.msBackingStorePixelRatio ||
              ctx.oBackingStorePixelRatio ||
              ctx.backingStorePixelRatio || 1;

    return dpr / bsr;
})();


createHiDPICanvas = function(w, h, ratio) {
    if (!ratio) { ratio = PIXEL_RATIO; }
    var can = document.createElement("canvas");
    can.width = w * ratio;
    can.height = h * ratio;
    can.style.width = w + "px";
    can.style.height = h + "px";
    can.getContext("2d").setTransform(ratio, 0, 0, ratio, 0, 0);
    return can;
}

//Create canvas with the device resolution.
var myCanvas = createHiDPICanvas(500, 250);

//Create canvas with a custom resolution.
var myCustomCanvas = createHiDPICanvas(500, 200, 4);

Mam nadzieję że to pomoże!

MyNameIsKo
źródło
2
Pomyślałem, że warto wspomnieć, że moja metoda createHiDPI () jest nieco słabo nazwana. DPI to termin tylko do druku, a PPI jest bardziej odpowiednim akronimem, ponieważ monitory wyświetlają obrazy za pomocą pikseli, a nie kropek.
MyNameIsKo
3
Zwróć uwagę, że ten współczynnik może się zmieniać w trakcie życia strony. Na przykład, jeśli przeciągnę okno Chrome ze starszego „standardowego” zewnętrznego monitora res na wbudowany ekran siatkówki Macbooka, kod obliczy inny współczynnik. Tylko do Twojej wiadomości, jeśli planujesz przechowywać tę wartość w pamięci podręcznej. (zewnętrzny był współczynnik 1, ekran siatkówki 2 na wypadek, gdybyś był ciekawy)
Aardvark
1
Dzięki za to wyjaśnienie. Ale co z zasobami graficznymi? Czy musimy dostarczać każdy obraz płótna w podwójnej rozdzielczości i ręcznie zmniejszać go?
Kokodoko,
1
Więcej ciekawostek: IE w Windows Phone 8 zawsze zgłasza 1 dla window.devicePixelRatio (a wywołanie kopii zapasowej nie działa). Wygląda okropnie na 1, ale stosunek 2 wygląda dobrze. Na razie moje obliczenia współczynnika i tak zwracają co najmniej 2 (kiepskie obejście, ale moje platformy docelowe to nowoczesne telefony, z których prawie wszystkie wydają się mieć ekrany o wysokiej rozdzielczości). Testowano na HTC 8X i Lumii 1020.
Aardvark
1
@Aardvark: wydaje się również, że msBackingStorePixelRatio jest zawsze niezdefiniowane. Zadałem nowe pytanie na ten temat tutaj: stackoverflow.com/questions/22483296/…
Frank TV
29

Rozwiązany!

Postanowiłem sprawdzić, jakie zmiany ustawiłem w atrybutach szerokości i wysokości , javascriptaby zobaczyć, jak to wpłynęło na rozmiar płótna - i tak się nie stało. Zmienia rozdzielczość.

Aby uzyskać pożądany wynik, musiałem również ustawić canvas.style.widthatrybut, który zmienia fizyczny rozmiar canvas:

canvas.width=1000;//horizontal resolution (?) - increase for better looking text
canvas.height=500;//vertical resolution (?) - increase for better looking text
canvas.style.width=width;//actual width of canvas
canvas.style.height=height;//actual height of canvas
Phil
źródło
1
Aby uzyskać idealne rezultaty, nie powinieneś w ogóle dotykać atrybutów stylu płótna i kontrolować rozmiar tylko za pomocą atrybutów szerokości i wysokości samego płótna. W ten sposób upewniasz się, że jeden piksel na płótnie jest równy jednemu pikselowi na ekranie.
Philipp
7
Nie zgadzam się. Zmiana atrybutów style.width / height jest dokładnie tym, jak tworzysz płótno HiDPI.
MyNameIsKo
2
W swojej odpowiedzi ustawiłeś canvas.width na 1000, a canvas.style.width na połowę przy 500. Działa to, ale tylko w przypadku urządzenia o współczynniku pikseli 2. W przypadku wszystkich poniższych elementów, takich jak monitor biurka, płótno jest teraz rysowanie do niepotrzebnych pikseli. W przypadku wyższych współczynników jesteś teraz w miejscu, w którym zacząłeś, z rozmytym zasobem / elementem o niskiej rozdzielczości. Kolejną kwestią, do której Philip wydawał się nawiązywać, jest to, że wszystko, co rysujesz w kontekście, musi być teraz przyciągane do podwójnej szerokości / wysokości, mimo że jest wyświetlane z połową tej wartości. Rozwiązaniem tego problemu jest podwojenie kontekstu płótna.
MyNameIsKo
3
Jest window.devicePixelRatioi jest dobrze zaimplementowany w większości nowoczesnych przeglądarek.
Chen
6

Zmieniam rozmiar elementu canvas poprzez css, aby zajął całą szerokość elementu nadrzędnego. Zauważyłem, że szerokość i wysokość mojego elementu nie są skalowane. Szukałem najlepszego sposobu na ustawienie odpowiedniego rozmiaru.

canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;

W ten prosty sposób Twoje płótno będzie idealnie ustawione, bez względu na to, jakiego ekranu użyjesz.

Adam Mańkowski
źródło
5

To w 100% rozwiązało to dla mnie:

var canvas = document.getElementById('canvas');
canvas.width = canvas.getBoundingClientRect().width;
canvas.height = canvas.getBoundingClientRect().height;

(jest zbliżony do rozwiązania Adama Mańkowskiego).

Basj
źródło
4

Zauważyłem szczegół nie wymieniony w innych odpowiedziach. Rozdzielczość kanwy jest obcięta do wartości całkowitych.

Domyślne wymiary rozdzielczości obszaru roboczego to canvas.width: 300i canvas.height: 150.

Na moim ekranie window.devicePixelRatio: 1.75.

Więc kiedy ustawiam canvas.height = 1.75 * 150wartość jest obcinana od żądanego 262.5do 262.

Rozwiązaniem jest wybranie wymiarów układu CSS dla danego elementu, window.devicePixelRatiotak aby nie doszło do obcięcia przy skalowaniu rozdzielczości.

Na przykład, mógłbym użyć width: 300pxi height: 152pxco dałoby liczby całkowite po pomnożeniu przez 1.75.

Edycja : Innym rozwiązaniem jest wykorzystanie faktu, że piksele CSS mogą być ułamkowe, aby przeciwdziałać obcięciu skalowanych pikseli płótna.

Poniżej znajduje się demo wykorzystujące tę strategię.

Edycja : Oto skrzypce OP zaktualizowane do tej strategii: http://jsfiddle.net/65maD/83/ .

main();

// Rerun on window resize.
window.addEventListener('resize', main);


function main() {
  // Prepare canvas with properly scaled dimensions.
  scaleCanvas();

  // Test scaling calculations by rendering some text.
  testRender();
}


function scaleCanvas() {
  const container = document.querySelector('#container');
  const canvas = document.querySelector('#canvas');

  // Get desired dimensions for canvas from container.
  let {width, height} = container.getBoundingClientRect();

  // Get pixel ratio.
  const dpr = window.devicePixelRatio;
  
  // (Optional) Report the dpr.
  document.querySelector('#dpr').innerHTML = dpr.toFixed(4);

  // Size the canvas a bit bigger than desired.
  // Use exaggeration = 0 in real code.
  const exaggeration = 20;
  width = Math.ceil (width * dpr + exaggeration);
  height = Math.ceil (height * dpr + exaggeration);

  // Set the canvas resolution dimensions (integer values).
  canvas.width = width;
  canvas.height = height;

  /*-----------------------------------------------------------
                         - KEY STEP -
   Set the canvas layout dimensions with respect to the canvas
   resolution dimensions. (Not necessarily integer values!)
   -----------------------------------------------------------*/
  canvas.style.width = `${width / dpr}px`;
  canvas.style.height = `${height / dpr}px`;

  // Adjust canvas coordinates to use CSS pixel coordinates.
  const ctx = canvas.getContext('2d');
  ctx.scale(dpr, dpr);
}


function testRender() {
  const canvas = document.querySelector('#canvas');
  const ctx = canvas.getContext('2d');
  
  // fontBaseline is the location of the baseline of the serif font
  // written as a fraction of line-height and calculated from the top
  // of the line downwards. (Measured by trial and error.)
  const fontBaseline = 0.83;
  
  // Start at the top of the box.
  let baseline = 0;

  // 50px font text
  ctx.font = `50px serif`;
  ctx.fillText("Hello World", 0, baseline + fontBaseline * 50);
  baseline += 50;

  // 25px font text
  ctx.font = `25px serif`;
  ctx.fillText("Hello World", 0, baseline + fontBaseline * 25);
  baseline += 25;

  // 12.5px font text
  ctx.font = `12.5px serif`;
  ctx.fillText("Hello World", 0, baseline + fontBaseline * 12.5);
}
/* HTML is red */

#container
{
  background-color: red;
  position: relative;
  /* Setting a border will mess up scaling calculations. */
  
  /* Hide canvas overflow (if any) in real code. */
  /* overflow: hidden; */
}

/* Canvas is green */ 

#canvas
{
  background-color: rgba(0,255,0,.8);
  animation: 2s ease-in-out infinite alternate both comparison;
}

/* animate to compare HTML and Canvas renderings */

@keyframes comparison
{
  33% {opacity:1; transform: translate(0,0);}
  100% {opacity:.7; transform: translate(7.5%,15%);}
}

/* hover to pause */

#canvas:hover, #container:hover > #canvas
{
  animation-play-state: paused;
}

/* click to translate Canvas by (1px, 1px) */

#canvas:active
{
  transform: translate(1px,1px) !important;
  animation: none;
}

/* HTML text */

.text
{
  position: absolute;
  color: white;
}

.text:nth-child(1)
{
  top: 0px;
  font-size: 50px;
  line-height: 50px;
}

.text:nth-child(2)
{
  top: 50px;
  font-size: 25px;
  line-height: 25px;
}

.text:nth-child(3)
{
  top: 75px;
  font-size: 12.5px;
  line-height: 12.5px;
}
<!-- Make the desired dimensions strange to guarantee truncation. -->
<div id="container" style="width: 313.235px; height: 157.122px">
  <!-- Render text in HTML. -->
  <div class="text">Hello World</div>
  <div class="text">Hello World</div>
  <div class="text">Hello World</div>
  
  <!-- Render text in Canvas. -->
  <canvas id="canvas"></canvas>
</div>

<!-- Interaction instructions. -->
<p>Hover to pause the animation.<br>
Click to translate the green box by (1px, 1px).</p>

<!-- Color key. -->
<p><em style="color:red">red</em> = HTML rendered<br>
<em style="color:green">green</em> = Canvas rendered</p>

<!-- Report pixel ratio. -->
<p>Device pixel ratio: <code id="dpr"></code>
<em>(physical pixels per CSS pixel)</em></p>

<!-- Info. -->
<p>Zoom your browser to re-run the scaling calculations.
(<code>Ctrl+</code> or <code>Ctrl-</code>)</p>

spenceryue
źródło
3

Lekko dostosowałem kod MyNameIsKo pod canvg (biblioteka SVG do Canvas js). Przez chwilę byłem zdezorientowany i spędziłem na tym trochę czasu. Mam nadzieję, że to komuś pomoże.

HTML

<div id="chart"><canvas></canvas><svg>Your SVG here</svg></div>

Javascript

window.onload = function() {

var PIXEL_RATIO = (function () {
    var ctx = document.createElement("canvas").getContext("2d"),
        dpr = window.devicePixelRatio || 1,
        bsr = ctx.webkitBackingStorePixelRatio ||
              ctx.mozBackingStorePixelRatio ||
              ctx.msBackingStorePixelRatio ||
              ctx.oBackingStorePixelRatio ||
              ctx.backingStorePixelRatio || 1;

    return dpr / bsr;
})();

setHiDPICanvas = function(canvas, w, h, ratio) {
    if (!ratio) { ratio = PIXEL_RATIO; }
    var can = canvas;
    can.width = w * ratio;
    can.height = h * ratio;
    can.style.width = w + "px";
    can.style.height = h + "px";
    can.getContext("2d").setTransform(ratio, 0, 0, ratio, 0, 0);
}

var svg = document.querySelector('#chart svg'),
    canvas = document.querySelector('#chart canvas');

var svgSize = svg.getBoundingClientRect();
var w = svgSize.width, h = svgSize.height;
setHiDPICanvas(canvas, w, h);

var svgString = (new XMLSerializer).serializeToString(svg);
var ctx = canvas.getContext('2d');
ctx.drawSvg(svgString, 0, 0, w, h);

}
LessWrong
źródło
2

Dla tych z Was, którzy pracują w actjs, dostosowałem odpowiedź MyNameIsKo i działa świetnie. Oto kod.

import React from 'react'

export default class CanvasComponent extends React.Component {
    constructor(props) {
        this.calcRatio = this.calcRatio.bind(this);
    } 

    // Use componentDidMount to draw on the canvas
    componentDidMount() {  
        this.updateChart();
    }

    calcRatio() {
        let ctx = document.createElement("canvas").getContext("2d"),
        dpr = window.devicePixelRatio || 1,
        bsr = ctx.webkitBackingStorePixelRatio ||
          ctx.mozBackingStorePixelRatio ||
          ctx.msBackingStorePixelRatio ||
          ctx.oBackingStorePixelRatio ||
          ctx.backingStorePixelRatio || 1;
        return dpr / bsr;
    }

    // Draw on the canvas
    updateChart() {

        // Adjust resolution
        const ratio = this.calcRatio();
        this.canvas.width = this.props.width * ratio;
        this.canvas.height = this.props.height * ratio;
        this.canvas.style.width = this.props.width + "px";
        this.canvas.style.height = this.props.height + "px";
        this.canvas.getContext("2d").setTransform(ratio, 0, 0, ratio, 0, 0);
        const ctx = this.canvas.getContext('2d');

       // now use ctx to draw on the canvas
    }


    render() {
        return (
            <canvas ref={el=>this.canvas=el} width={this.props.width} height {this.props.height}/>
        )
    }
}

W tym przykładzie podaję szerokość i wysokość płótna jako rekwizyty.

jrobins
źródło
2

Wypróbuj ten jeden wiersz CSS na swoim płótnie: image-rendering: pixelated

Zgodnie z MDN :

Podczas skalowania obrazu w górę należy użyć algorytmu najbliższego sąsiada, aby obraz wyglądał na złożony z dużych pikseli.

W ten sposób całkowicie zapobiega wygładzaniu krawędzi.

1valdis
źródło
2

Chociaż odpowiedź @ MyNameIsKo nadal działa, jest teraz trochę przestarzała w 2020 roku i można ją ulepszyć:

function createHiPPICanvas(w, h) {
    let ratio = window.devicePixelRatio;
    let cv = document.createElement("canvas");
    cv.width = w * ratio;
    cv.height = h * ratio;
    cv.style.width = w + "px";
    cv.style.height = h + "px";
    cv.getContext("2d").scale(ratio, ratio);
    return cv;
}

Ogólnie wprowadzamy następujące ulepszenia:

  • Usuwamy backingStorePixelRatioodniesienia, ponieważ nie są one tak naprawdę zaimplementowane w żadnej przeglądarce w żaden istotny sposób (w rzeczywistości tylko Safari zwraca coś innego niż undefined, a ta wersja nadal działa doskonale w Safari);
  • Zastępujemy cały ten kod współczynnika window.devicePixelRatio, który ma fantastyczne wsparcie
  • Oznacza to również, że deklarujemy o jedną globalną własność mniej --- hura !!
  • Możemy również usunąć || 1fallback na window.devicePixelRatio, jak to jest bez sensu: wszystkich przeglądarek, które nie obsługują tej właściwości nie obsługuje .setTransformlub .scaleteż, więc tej funkcji nie będzie działać na nich, awaryjna, czy nie;
  • Możemy zastąpić .setTransformprzez .scale, ponieważ podanie szerokości i wysokości jest nieco bardziej intuicyjne niż przekazanie macierzy transformacji.
  • Nazwa funkcji została zmieniona z createHiDPICanvasna createHiPPICanvas. Jak sam @MyNameIsKo wspomina w swoich komentarzach, DPI (Dots per Inch) drukuje terminologię (ponieważ drukarki tworzą obrazy z małych kropek kolorowego atramentu). Chociaż podobnie, monitory wyświetlają obrazy za pomocą pikseli i jako takie PPI (piksele na cal) jest lepszym akronimem dla naszego przypadku użycia.

Dużą zaletą tych uproszczeń jest to, że ta odpowiedź może być teraz używana w TypeScript bez // @ts-ignore(ponieważ TS nie ma typów dla backingStorePixelRatio).

Toastrackenigma
źródło
0

W moim przypadku tylko połączenie różnych technik „pixel perfect” pomogło zarchiwizować wyniki:

  1. Pobierz i skaluj ze współczynnikiem pikseli zgodnie z sugestią @MyNameIsKo.

    pixelRatio = window.devicePixelRatio / ctx.backingStorePixelRatio

  2. Skaluj płótno przy zmianie rozmiaru (unikaj domyślnego rozciągania płótna).

  3. pomnóż lineWidth za pomocą pixelRatio, aby znaleźć właściwą `` rzeczywistą '' grubość linii w pikselach:

    context.lineWidth = grubość * pixelRatio;

  4. Sprawdź, czy grubość linii jest nieparzysta czy parzysta. dodaj połowę pixelRatio do pozycji linii dla nieparzystych wartości grubości.

    x = x + pixelRatio / 2;

Nieparzysta linia zostanie umieszczona na środku piksela. Powyższa linia służy do jego niewielkiego przesunięcia.

function getPixelRatio(context) {
  dpr = window.devicePixelRatio || 1,
    bsr = context.webkitBackingStorePixelRatio ||
    context.mozBackingStorePixelRatio ||
    context.msBackingStorePixelRatio ||
    context.oBackingStorePixelRatio ||
    context.backingStorePixelRatio || 1;

  return dpr / bsr;
}


var canvas = document.getElementById('canvas');
var context = canvas.getContext("2d");
var pixelRatio = getPixelRatio(context);
var initialWidth = canvas.clientWidth * pixelRatio;
var initialHeight = canvas.clientHeight * pixelRatio;


window.addEventListener('resize', function(args) {
  rescale();
  redraw();
}, false);

function rescale() {
  var width = initialWidth * pixelRatio;
  var height = initialHeight * pixelRatio;
  if (width != context.canvas.width)
    context.canvas.width = width;
  if (height != context.canvas.height)
    context.canvas.height = height;

  context.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
}

function pixelPerfectLine(x) {

  context.save();
  context.beginPath();
  thickness = 1;
  // Multiple your stroke thickness  by a pixel ratio!
  context.lineWidth = thickness * pixelRatio;

  context.strokeStyle = "Black";
  context.moveTo(getSharpPixel(thickness, x), getSharpPixel(thickness, 0));
  context.lineTo(getSharpPixel(thickness, x), getSharpPixel(thickness, 200));
  context.stroke();
  context.restore();
}

function pixelPerfectRectangle(x, y, w, h, thickness, useDash) {
  context.save();
  // Pixel perfect rectange:
  context.beginPath();

  // Multiple your stroke thickness by a pixel ratio!
  context.lineWidth = thickness * pixelRatio;
  context.strokeStyle = "Red";
  if (useDash) {
    context.setLineDash([4]);
  }
  // use sharp x,y and integer w,h!
  context.strokeRect(
    getSharpPixel(thickness, x),
    getSharpPixel(thickness, y),
    Math.floor(w),
    Math.floor(h));
  context.restore();
}

function redraw() {
  context.clearRect(0, 0, canvas.width, canvas.height);
  pixelPerfectLine(50);
  pixelPerfectLine(120);
  pixelPerfectLine(122);
  pixelPerfectLine(130);
  pixelPerfectLine(132);
  pixelPerfectRectangle();
  pixelPerfectRectangle(10, 11, 200.3, 443.2, 1, false);
  pixelPerfectRectangle(41, 42, 150.3, 443.2, 1, true);
  pixelPerfectRectangle(102, 100, 150.3, 243.2, 2, true);
}

function getSharpPixel(thickness, pos) {

  if (thickness % 2 == 0) {
    return pos;
  }
  return pos + pixelRatio / 2;

}

rescale();
redraw();
canvas {
  image-rendering: -moz-crisp-edges;
  image-rendering: -webkit-crisp-edges;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
  width: 100vh;
  height: 100vh;
}
<canvas id="canvas"></canvas>

Zdarzenie zmiany rozmiaru nie jest uruchamiane w wycinanym pliku, więc możesz wypróbować plik na githubie

Ievgen Naida
źródło
0

Dla mnie to nie tylko obraz, ale i tekst miał kiepską jakość. Najprostszym rozwiązaniem działającym w różnych przeglądarkach dla wyświetlaczy Retina / non-Retina było renderowanie obrazu dwukrotnie większego niż zamierzano i skalowanie kontekstu płótna, jak sugerował ten facet: https://stackoverflow.com/a/53921030/4837965

Polina Potrebko
źródło