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

126

REMARQUE : cela a à voir avec la façon dont les éléments de canevas existants sont rendus lors de la mise à l'échelle , pas avec la façon dont les lignes ou les graphiques sont rendus sur une surface de canevas . En d'autres termes, cela a tout à voir avec l' interpolation des éléments mis à l'échelle , et rien à voir avec l' anticrénelage des graphiques dessinés sur une toile. Je ne suis pas préoccupé par la façon dont le navigateur trace des lignes; Je me soucie de la façon dont le navigateur rend l'élément de toile lui - même lorsqu'il est mis à l'échelle.


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

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

L'application sera un jeu de style 8 bits «rétro» écrit en HTML5 + JS pour préciser ce dont j'ai besoin.


Pour illustrer, voici un exemple. ( version en direct )

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 ceci:

$('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 est ce que Chromium (14.0) rend. L'image de droite est ce que je veux (dessinée à la main à des fins d'illustration).

Chrome interpole les éléments de canevas mis à l'échelle Une version non interpolée

Namuol
la source
1
Un peu lié, mais pas identique: stackoverflow.com/questions/195262/…
HostileFork dit ne pas faire confiance
1
Je crois que cette question fait référence aux fonctions de dessin au trait dessinant des lignes anti-crénelées. Je fais référence à la façon dont les éléments de canevas redimensionnés sont rendus avec interpolation par défaut.
namuol
Oui, désolé ... J'ai d'abord pensé aux questions les mêmes, puis j'ai immédiatement remarqué mon erreur. : - / Et si vous essayiez d'utiliser le filtre de pixellisation de jsmanipulate? joelb.me/jsmanipulate
HostileFork dit de ne pas faire confiance à SE
1
C'est plus un filtre d'image destiné aux photographies.
namuol
Oui, mais s'il transforme votre image de gauche en une version suffisamment bonne de votre image de droite pour vous rendre heureux, il pourrait fournir une solution multi-navigateurs (étant donné que cela ne semble pas trop prometteur de trouver un drapeau pour ce mode de dessin, universel ou autre , dans WebKit). Que ce soit un substitut viable dépend du problème que vous essayez de résoudre ... si vous avez vraiment besoin d'un dessin de pixels discret ou si vous essayez simplement de vous assurer que le résultat est pixélisé avec une certaine granularité.
HostileFork dit de ne pas faire confiance à SE

Réponses:

126

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

Existe-t-il une propriété de canevas ou un paramètre de navigateur que je peux 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 l'instant, vous devrez recourir à des hack-around pour obtenir ce que vous voulez.


image-rendering

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

La propriété de rendu d'image fournit une indication à l'agent utilisateur sur les aspects d'une image qu'il est le plus important de préserver lorsque l'image est mise à l'échelle, pour aider l'agent utilisateur dans le choix d'un algorithme de mise à l'échelle approprié.

La spécification définit trois valeurs acceptées: auto, crisp-edgeset pixelated.

pixélisé:

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

La norme? Cross-browser?

Puisqu'il ne s'agit que d'un projet de travail , il n'y a aucune garantie que cela deviendra la norme. La prise en charge du navigateur est actuellement inégale, au mieux.

Le Mozilla Developer Network a une page assez complète consacrée à l'état actuel de l'art que je recommande vivement de lire.

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

Mise à jour: 2014-09-12

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

Firefox a un rapport de bogue ouvert pour être image-rendering: pixelatedimplémenté, mais -moz-crisp-edgesfonctionne pour le moment.

Solution?

La solution CSS la plus multiplateforme à ce jour est donc:

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 plates-formes HTML5 (Chrome, en particulier).

Bien sûr, on pourrait redimensionner manuellement les images en utilisant l'interpolation du plus proche voisin sur des surfaces de canevas haute résolution en javascript, ou même des images pré-échelle côté serveur, mais dans mon cas, cela sera extrêmement coûteux, ce n'est donc pas une option viable.

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

Voir la réponse de Simon pour une solution basée sur le canevas qui repose sur la imageSmoothingEnabledpropriété (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 discutées dans l'article MDN sur les canvaséléments, j'ai fait ce violon qui devrait afficher quelque chose comme ça, flou ou non, en fonction de votre navigateur: une image 4: 1 (64x64 à 256x256) d'un téléviseur isométrique de style pixel art

Namuol
la source
Le rendu d'image semble fonctionner ailleurs, mais les navigateurs Webkit. J'espère vraiment que le personnel de Chrome / Chromium / Safari lit bien ce que signifie "passer à l'interpolation EPX dans des situations de faible charge". Quand j'ai lu la phrase pour la première fois, j'ai pensé que l'optimisation du contraste permettait de créer de nouvelles couleurs à faible charge, mais NON: en.wikipedia.org/wiki/...
Timo Kähkönen
2
Opera 12.02 sur Mac et Windows prend en charge le rendu d'image: -o-crisp-bords ( jsfiddle.net/VAXrL/21 ), vous pouvez donc l'ajouter à votre réponse.
Timo Kähkönen
Est-il censé fonctionner lorsque j'augmente le zoom du navigateur? J'ai testé le dessin avec FF 22, Chrome 27 et IE 10 sur Windows 7 lorsque le zoom du navigateur est de 150% et que le résultat est flou sur tous les navigateurs. Lorsque je double la largeur et la hauteur de la toile et que je la remets à la largeur et à la hauteur d'origine avec CSS, cela dessine des lignes nettes.
pablo
C'est une bonne question. Je ne sais pas s'il existe une spécification pour le comportement de mise à l'échelle spécifié par l'utilisateur.
namuol
1
Je suis (du futur présent!) Sur Chrome 78. Je vois "rendu d'image: pixelisé;" travail. Il semble que cela a été ajouté en 2015 à Chrome 41: développeurs.google.com
web
61

Nouvelle réponse 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, qui prend la valeur par défaut trueet détermine si les images dessinées sur des coordonnées non entières ou mises à l'échelle utiliseront un algorithme plus fluide. S'il est défini sur falsealors le plus proche voisin est utilisé, produisant une image moins lisse et à la place, faisant simplement des pixels plus grands.

Le lissage d'image n'a été ajouté que récemment à la spécification du canevas et n'est pas pris en charge par tous les navigateurs, mais certains navigateurs ont implémenté des versions préfixées par le fournisseur de cette propriété. Dans le contexte existant mozImageSmoothingEnableddans Firefox, webkitImageSmoothingEnabledChrome et Safari, les définir sur false empêchera l'anti-aliasing de se produire. Malheureusement, au moment de la rédaction de cet article, IE9 et Opera n'ont pas implémenté cette propriété, préfixée par le fournisseur ou autre.


Aperçu: JSFiddle

Résultat:

entrez la description de l'image ici

Simon Sarris
la source
1
Malheureusement, il semble que cela ne fonctionne pas encore. Peut-être que je manque quelque chose? Voici un test: jsfiddle.net/VAXrL/187
namuol
12
vous devez mettre à l'échelle à l'aide de l'API de canevas pour que cela fonctionne, vous ne pouvez jamais évoluer avec CSS et que l'API de canevas l'affecte! Donc quelque chose comme ceci: jsfiddle.net/VAXrL/190
Simon Sarris
1
Mais la nature de ce problème ne concerne pas vraiment la toile. Il s'agit de la façon dont le navigateur rend les images mises à l'échelle, y compris les éléments <canvas>.
namuol
4
Si vous mettez à l'échelle en utilisant CSS, la solution de namuol fonctionnera. Si vous redimensionnez manuellement l'image sur le canevas, la solution de Simon fonctionne. C'est chouette qu'il existe des solutions pour les deux cas, alors merci à vous deux!
Shaun Lebron
Pour IE 11: msImageSmoothingEnabled fonctionne pour moi! msdn.microsoft.com/en-us/library/dn265062(v=vs.85).aspx
steve
11

Edit 7/31/2012 - Cette fonctionnalité est maintenant dans les spécifications du canevas! Voir la réponse séparée ici:

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

L'ancienne réponse est ci-dessous pour la postérité.


En fonction de l'effet souhaité, 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:

entrez la description de l'image ici

Probablement pas ce que vous voulez. Mais si vous cherchiez simplement à n'avoir aucun flou, ce serait le ticket, alors je vous l'offrirai au cas où.

Une option plus difficile consiste à utiliser la manipulation des 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 seuls ne vous aideront pas ici pour mettre à l'échelle l'un à l'autre avec l'effet exact que vous désirez.

Simon Sarris
la source
Ouais, c'est ce dont j'avais peur. Apparemment, image-rendering: -webkit-optimize-contrastça devrait faire l'affaire mais ne semble rien faire. Je peux envisager de lancer une fonction pour agrandir le contenu d'un canevas en utilisant l'interpolation du plus proche voisin.
namuol
J'ai initialement accepté votre réponse; Je l'ai révoqué pour le moment car il semble y avoir confusion quant à savoir s'il s'agit d'un double ou non.
namuol
J'ai une réponse plus complète après avoir fait quelques recherches et je voudrais rouvrir la question pour fournir ma réponse.
namuol
3

Dans Google Chrome, les modèles 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);
Saviski
la source
C'est la solution de contournement la plus attrayante que j'ai vue jusqu'à présent. Je ne suis pas encore sûr des performances, mais cela vaut vraiment la peine d'être examiné. Je vais l'essayer.
namuol
l'utilisation de la répétition au lieu de la non-répétition est plus rapide. Je ne sais pas pourquoi. C'est aussi assez rapide, three.js l'utilise dans CanvasRenderer
saviski
6
mise à jour: ne fonctionne plus avec chrome 21 (en raison de l'ajout de la prise en charge de l'affichage Retina?) nouvelle version jsfiddle.net/saviski/pGs4f/12 utilisant imageSmoothingEnabled pointé par Simon
saviski
Lorsque la résolution Retina devient générale, les choses changent énormément. Lorsque l'écran Retina de 326 dpi (128dpcm) de style iPhone4-5 arrive sur des moniteurs de 24 pouces (52x32cm), la taille de l'écran sera de 6656 x 4096 px. L'anticrénelage est alors mauvais et tous les fournisseurs de navigateurs (même basés sur Webkit) sont obligés d'autoriser la désactivation de l'anticrénelage. L'anticrénelage est un fonctionnement intensif en CPU et l'anticrénelage d'une image de 6656 x 4096 px serait trop lent, mais heureusement inutile dans un tel affichage.
Timo Kähkönen
1
Pour rappel, voici un bon point de repère sur les modèles par rapport à l'image: jsperf.com/drawimage-vs-canvaspattern/9
yckart
1

La solution de contournement de Saviski expliquée ici est prometteuse, car elle fonctionne sur:

  • Chrome 22.0.1229.79 Mac OS X 10.6.8
  • Chrome 22.0.1229.79 m Windows 7
  • Chromium 18.0.1025.168 (build développeur 134367 Linux) Ubuntu 11.10
  • Firefox 3.6.25 Windows 7

Mais 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 (le rendu d'image: -moz-crisp-bords fonctionne dans ce )
  • Opera 12.02 Mac OS X 10.6.8 (le rendu d'image: -o-crisp-bords fonctionne dans ce )
  • Opera 12.02 Windows 7 (rendu d'image: -o-crisp-bords fonctionne dans ce cas )

Les problèmes sont les suivants, car ctx.XXXImageSmoothingEnabled ne fonctionne pas et le rendu d'image ne fonctionne pas:

  • Safari 5.1.7 Mac OS X 10.6.8. (rendu d'image: -webkit-Optimize-contraste ne fonctionne PAS)
  • Safari 5.1.7 Windows 7 (rendu d'image: -webkit-Optimize-Contraste NE FONCTIONNE PAS)
  • IE 9 Windows 7 (-ms-interpolation-mode: le plus proche voisin ne fonctionne PAS)
Timo Kähkönen
la source