web-dev-qa-db-fra.com

Désactiver l'interpolation lors de la mise à l'échelle d'un <canvas>

[~ # ~] note [~ # ~]: Cela concerne la façon dont existant les éléments de toile sont rendus lors de la mise à l'échelle, pas à faire avec la façon dont les lignes ou les graphiques sont rendus sur une surface de toile. En d'autres termes, cela a tout à voir avec interpolation de éléments mis à l'échelle, et rien à voir avec antialiasing de graphismes être dessiné sur une toile. Je ne suis pas concerné par la façon dont le navigateur trace des lignes; Je me soucie de la façon dont le navigateur rend l'élément canvas lui-même quand il est mis à l'échelle.


Existe-t-il une propriété de toile ou un paramètre de navigateur que je puisse modifier par programmation pour désactiver l’interpolation lors de la mise à l’échelle <canvas> éléments? Une solution multi-navigateurs est idéale mais pas indispensable. Les navigateurs Webkit sont ma cible principale. La performance est très importante.

Cette question est très similaire mais ne montre pas suffisamment le problème. Pour ce que ça vaut, j'ai essayé image-rendering: -webkit-optimize-contrast en vain.

L'application sera un jeu "rétro" de style 8 bits, écrit en HTML5 + JS, pour bien comprendre ce dont j'ai besoin.


Pour illustrer, voici un exemple. ( version live )

Supposons que j'ai une toile 21x21 ...

<canvas id='b' width='21' height='21'></canvas>

... qui a css qui rend l'élément 5 fois plus grand (105x105):

canvas { border: 5px solid #ddd; }
canvas#b { width: 105px; height: 105px; } /* 5 * 21 = 105 */

Je dessine un simple 'X' sur la toile comme suit:

$('canvas').each(function () {
    var ctx = this.getContext("2d");
    ctx.moveTo(0,0);
    ctx.lineTo(21,21);
    ctx.moveTo(0,21);
    ctx.lineTo(21,0);
    ctx.stroke();
});

L'image de gauche correspond à ce que rend Chrome (14.0). L’image de droite correspond à ce que je veux (dessinée à la main à des fins d’illustration).

Chrome interpolates scaled canvas elementsA non-interpolated version

120
namuol

Dernière mise à jour: 2014-09-12

Existe-t-il une propriété de canevas ou un paramètre de navigateur que je puisse modifier par programmation pour désactiver l’interpolation lors de la mise à l’échelle des éléments?

La réponse est peut-être un jour. Pour le moment, vous devrez recourir à des bidouilles pour obtenir ce que vous voulez.


image-rendering

Le brouillon de CSS3 décrit une nouvelle propriété, image-rendering _ qui devrait faire ce que je veux:

La propriété de rendu d'image indique à l'agent utilisateur quels aspects d'une image sont les plus importants à préserver lors de la mise à l'échelle de l'image, afin d'aider l'agent utilisateur à choisir un algorithme de mise à l'échelle approprié.

La spécification décrit trois valeurs acceptées: auto, crisp-edges, et pixelated.

pixellisé:

Lors de la mise à l'échelle de l'image, le "voisin le plus proche" ou un algorithme similaire doit être utilisé, de sorte que l'image semble simplement composée de très grands pixels. Lorsque vous réduisez, ceci est identique à auto.

La norme? Cross-browser?

Comme il ne s'agit que d'un brouillon de travail, rien ne garantit que cela deviendra la norme. Le support du navigateur est actuellement inégal, au mieux.

Le réseau de développeurs Mozilla a ne page assez complète consacrée à l’état actuel des connaissances que je recommande vivement de lire.

Les développeurs de Webkit ont initialement choisi de implémenter provisoirement ceci en tant que -webkit-optimize-contrast , mais Chromium/Chrome ne semble pas utiliser une version de Webkit qui l'implémente.

Mise à jour: 2014-09-12

Chrome 38 prend désormais en charge image-rendering: pixelated !

Firefox a un rapport de bogue ouvert pour obtenir image-rendering: pixelated implémenté, mais -moz-crisp-edges fonctionne pour le moment.

Solution?

À ce jour, la solution la plus multi-plateforme et uniquement CSS est la suivante:

canvas {
  image-rendering: optimizeSpeed;             /* Older versions of FF          */
  image-rendering: -moz-crisp-edges;          /* FF 6.0+                       */
  image-rendering: -webkit-optimize-contrast; /* Safari                        */
  image-rendering: -o-crisp-edges;            /* OS X & Windows Opera (12.02+) */
  image-rendering: pixelated;                 /* Awesome future-browsers       */
  -ms-interpolation-mode: nearest-neighbor;   /* IE                            */
}

Malheureusement, cela ne fonctionnera pas encore sur toutes les principales plateformes HTML5 (Chrome, en particulier).

Bien sûr, on pourrait redimensionner manuellement les images en utilisant une interpolation du voisin le plus proche sur des surfaces de canevas haute résolution en javascript, ou même des images de pré-dimensionnement côté serveur, mais dans mon cas, cela sera excessivement coûteux et ne sera donc pas une option viable.

ImpactJS utilise une technique de pré-dimensionnement de la texture pour contourner tout ce FUD. Le développeur de l'Impact, Dominic Szablewski, a écrit un article très détaillé à ce sujet (il a même fini par citer cette question dans ses recherches).

Voir réponse de Simon pour une solution basée sur une toile qui repose sur la propriété imageSmoothingEnabled (non disponible dans les anciens navigateurs, mais plus simple que la pré-mise à l'échelle et assez largement prise en charge).

Démo en direct

Si vous souhaitez tester les propriétés CSS décrites dans l'article de MDN sur les éléments canvas, j'ai fait ce violon qui devrait afficher quelque chose comme ceci, flou ou non, selon ton navigateur: a 4:1 (64x64 to 256x256) image of an isometric pixel-art style TV

119
namuol

Nouvelle réponse le 31/07/2012

C'est enfin dans les spécifications de la toile!

La spécification a récemment ajouté une propriété appelée imageSmoothingEnabled, dont la valeur par défaut est true et qui détermine si les images dessinées sur des coordonnées non entières ou à l'échelle utiliseront un algorithme plus lisse. S'il est défini sur false, le voisin le plus proche est utilisé, ce qui produit une image moins lisse et ne crée que des pixels plus volumineux.

Le lissage des images n’a été ajouté que récemment à la spécification de la grille et n’est pas pris en charge par tous les navigateurs, mais certains d'entre eux ont implémenté des versions avec préfixe du vendeur de cette propriété. Sur le contexte, il existe mozImageSmoothingEnabled dans Firefox et webkitImageSmoothingEnabled in Chrome et Safari, et leur attribuer la valeur false afin d’empêcher l’anti-aliasing de se produire. Malheureusement, à Au moment de la rédaction, IE9 et Opera n’ont pas implémenté cette propriété, qu’il soit préfixé par le fournisseur ou non.


Aperçu: JSFiddle

Résultat:

enter image description here

58
Simon Sarris

Éditer 31/07/2012 - Cette fonctionnalité est maintenant dans les spécifications de la toile! Voir la réponse séparée ici:

https://stackoverflow.com/a/11751817/154112

Ancienne réponse est ci-dessous pour la postérité.


En fonction de l'effet que vous souhaitez, vous avez ceci comme une option:

var can = document.getElementById('b');
var ctx = can.getContext('2d');
ctx.scale(5,5);
$('canvas').each(function () {
    var ctx = this.getContext("2d");
    ctx.moveTo(0,0);
    ctx.lineTo(21,21);
    ctx.moveTo(0,21);
    ctx.lineTo(21,0);
    ctx.stroke();
});

http://jsfiddle.net/wa95p/

Ce qui crée ceci:

enter image description here

Probablement pas ce que tu veux. Mais si vous cherchiez simplement à avoir le flou zéro, alors ce serait le billet, donc je vous l'offrirai au cas où.

Une option plus difficile consiste à utiliser la manipulation de pixels et à écrire vous-même un algorithme pour le travail. Chaque pixel de la première image devient un bloc de 5x5 pixels sur la nouvelle image. Ce ne serait pas trop difficile à faire avec imagedata.

Mais Canvas et CSS à eux seuls ne vous aideront pas à passer l'un de l'autre à l'effet voulu.

11
Simon Sarris

Dans Google Chrome, les motifs d'image de canevas ne sont pas interpolés.

Voici un exemple de travail édité à partir de la réponse namuol http://jsfiddle.net/pGs4f/

ctx.scale(4, 4);
ctx.fillStyle = ctx.createPattern(image, 'repeat');
ctx.fillRect(0, 0, 64, 64);
4
saviski

solution de contournement de Saviski expliqué ici est prometteur, car il fonctionne sur:

  • Chrome 22.0.1229.79 Mac OS X 10.6.8
  • Chrome 22.0.1229.79 m Windows 7
  • Chrome 18.0.1025.168 (Developer Build 134367 Linux) Ubuntu 11.10
  • Firefox 3.6.25 Windows 7

Mais cela ne fonctionne pas dans ce qui suit, mais le même effet peut être obtenu en utilisant le rendu d'image CSS:

  • Firefox 15.0.1 Mac OS X 10.6.8 (rendu d'image: -moz-crisp-edge fonctionne dans this )
  • Opera 12.02 Mac OS X 10.6.8 (rendu d'image: -o-crisp-edge fonctionne en this )
  • Opera 12.02 Windows 7 (rendu d'image: -o-crisp-edge fonctionne en this )

Les problèmes sont ceux-ci, parce que ctx.XXXImageSmoothingEnabled ne fonctionne pas et que le rendu d'image ne fonctionne pas:

  • Safari 5.1.7 Mac OS X 10.6.8. (rendu d'image: -webkit-optim-contrast-NOT ne fonctionne pas)
  • Safari 5.1.7 Windows 7 (rendu d'image: -webkit-optim-contrast-fonctionne PAS)
  • IE 9 Windows 7 (-ms-interpolation-mode: le plus proche voisin ne fonctionne pas)
1
Timo Kähkönen