Desativar interpolação ao dimensionar uma <canvas>


126

NOTA : Isso tem a ver com a forma como os elementos da tela existentes são renderizados quando redimensionados , não com a maneira como as linhas ou os gráficos são renderizados em uma superfície da tela . Em outras palavras, isso tem tudo a ver com interpolação de elementos dimensionados e nada a ver com antialiasing de gráficos desenhados em uma tela. Não estou preocupado com o modo como o navegador desenha linhas; Preocupo-me com a forma como o navegador renderiza o próprio elemento da telaquando é ampliado.


Existe uma propriedade da tela ou uma configuração do navegador que eu possa alterar programaticamente para desativar a interpolação ao dimensionar <canvas>elementos? Uma solução entre navegadores é ideal, mas não essencial; Navegadores baseados em Webkit são meu principal alvo. O desempenho é muito importante.

Essa pergunta é muito semelhante, mas não ilustra o problema suficientemente. Pelo que vale a pena, tentei image-rendering: -webkit-optimize-contrastsem sucesso.

O aplicativo será um jogo com estilo "retro" de 8 bits, escrito em HTML5 + JS para deixar claro o que eu preciso.


Para ilustrar, aqui está um exemplo. ( versão ao vivo )

Suponha que eu tenho uma tela 21x21 ...

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

... com css que torna o elemento 5 vezes maior (105x105):

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

Eu desenho um simples 'X' na tela assim:

$('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();
});

A imagem à esquerda é o que o Chromium (14.0) renderiza. A imagem à direita é o que eu quero (desenhada à mão para fins ilustrativos).

O Chrome interpola elementos de tela em escala Uma versão não interpolada


1
Um pouco relacionado, embora não idêntico: stackoverflow.com/questions/195262/…
HostileFork diz que não confia em SE

1
Acredito que essa pergunta esteja se referindo às funções de desenho de linhas que desenham linhas sem serrilhado. Estou me referindo a como os elementos redimensionados da tela são renderizados com interpolação por padrão.
namuol

Sim, desculpe ... Primeiro pensei nas perguntas da mesma forma e imediatamente notei meu erro. : - / E se você tentasse usar o filtro de pixelização do jsmanipulate? joelb.me/jsmanipulate
HostileFork disse que não confia em SE

1
É mais um filtro de imagem destinado a fotografias.
namuol

Sim, mas se transformar sua imagem esquerda em uma versão boa o suficiente para fazê-lo feliz, poderá fornecer uma solução entre navegadores (considerando que não parece muito promissor encontrar um sinalizador para esse modo de desenho, universal ou não , no WebKit). Se é um substituto viável, depende do problema que você está tentando resolver ... se você realmente precisa de um desenho discreto de pixel ou se está apenas tentando garantir que o resultado seja pixelizado com uma certa granularidade.
HostileFork diz que não confia em SE

Respostas:


126

Ultima atualização: 2014-09-12

Existe uma propriedade de tela ou configuração do navegador que eu possa alterar programaticamente para desativar a interpolação ao dimensionar elementos?

A resposta é talvez algum dia . Por enquanto, você terá que recorrer a truques para conseguir o que deseja.


image-rendering

O rascunho de trabalho do CSS3 descreve uma nova propriedade,image-rendering que deve fazer o que eu quero:

A propriedade de renderização de imagem fornece uma dica para o agente do usuário sobre quais aspectos da imagem são mais importantes para preservar quando a imagem é dimensionada, para ajudar o agente na escolha de um algoritmo de dimensionamento apropriado.

A especificação descreve três valores aceitos: auto, crisp-edges, e pixelated.

pixelizada:

Ao ampliar a imagem, o "vizinho mais próximo" ou algoritmo semelhante deve ser usado, para que a imagem pareça ser simplesmente composta de pixels muito grandes. Ao reduzir, é o mesmo que automático.

Padrão? Cross-browser?

Como esse é apenas um rascunho de trabalho , não há garantia de que isso se tornará padrão. Atualmente, o suporte ao navegador é irregular, na melhor das hipóteses.

A Mozilla Developer Network possui uma página bastante completa, dedicada ao estado da arte atual, que eu recomendo a leitura.

Os desenvolvedores do Webkit inicialmente optaram por implementar isso provisoriamente como-webkit-optimize-contrast , mas o Chromium / Chrome não parece estar usando uma versão do Webkit que o implementa.

Atualização: 12/09/2014

O Chrome 38 agora é compatívelimage-rendering: pixelated !

O Firefox tem um relatório de erros aberto para ser image-rendering: pixelatedimplementado, mas -moz-crisp-edgesfunciona por enquanto.

Solução?

A solução mais CSS, entre plataformas, até agora é:

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                            */
}

Infelizmente, isso ainda não funciona em todas as principais plataformas HTML5 (Chrome, em particular).

Obviamente, é possível dimensionar manualmente as imagens usando a interpolação do vizinho mais próximo em superfícies de tela de alta resolução em javascript ou até pré-dimensionar as imagens no servidor, mas no meu caso isso será proibitivamente caro, por isso não é uma opção viável.

O ImpactJS usa uma técnica de pré-escala de textura para contornar todo esse FUD. O desenvolvedor do Impact, Dominic Szablewski, escreveu um artigo muito aprofundado sobre isso (ele acabou citando essa pergunta em sua pesquisa).

Veja a resposta de Simon para uma solução baseada em tela que depende da imageSmoothingEnabledpropriedade (não disponível em navegadores antigos, mas mais simples que em pré-dimensionamento e com suporte bastante amplo).

Demonstração ao vivo

Se você quiser testar as propriedades CSS discutidas no artigo MDN sobre os canvaselementos, criei esse violino que deve exibir algo como isto, embaçado ou não, dependendo do seu navegador: uma imagem 4: 1 (64x64 a 256x256) de uma TV isométrica com estilo pixel-art


A renderização de imagem parece funcionar em outro lugar, mas nos navegadores de kit da web. Eu realmente espero que a equipe do Chrome / Chromium / Safari leia bem o que significa "alternar para interpolação EPX em situações de baixa carga". Quando li a frase pela primeira vez, pensei que o contraste otimizado permite novas cores criadas com pouca carga, mas NÃO: pt.wikipedia.org/wiki/…
Timo Kähkönen 7/10/12

2
O Opera 12.02 no Mac e Windows suporta renderização de imagem: -o-crisp- edge ( jsfiddle.net/VAXrL/21 ), para que você possa adicioná-lo à sua resposta.
Timo Kähkönen 8/10/12

Ele deve funcionar quando estou aumentando o zoom do navegador? Testei o desenho com FF 22, Chrome 27 e IE 10 no Windows 7 quando o zoom do navegador é de 150% e o resultado é desfocado em todos os navegadores. Quando duplico a largura e a altura da tela e a coloco de volta à largura e altura originais com CSS, ele desenha linhas nítidas.
Pablo

Esta é uma boa pergunta. Não tenho certeza se há uma especificação para o comportamento de escala especificado pelo usuário.
namuol

1
Estou (no futuro presente!) No Chrome 78. Estou vendo "renderização de imagem: pixelizada;" trabalhando. Parece que isso foi adicionado em 2015 ao Chrome 41: developers.google.com/web/updates/2015/01/pixelated
MrColes

61

Nova resposta 31/07/2012

Esta é finalmente a especificação da tela!

A especificação adicionou recentemente uma propriedade chamada imageSmoothingEnabled, que padroniza truee determina se as imagens desenhadas em coordenadas não inteiras ou desenhadas em escala usarão um algoritmo mais suave. Se estiver definido como o falsevizinho mais próximo, será usado, produzindo uma imagem menos suave e produzindo pixels com aparência maior.

A suavização de imagem foi recentemente adicionada à especificação de tela e não é suportada por todos os navegadores, mas alguns navegadores implementaram versões dessa propriedade com prefixo de fornecedor. No contexto, existe mozImageSmoothingEnabledno Firefox e webkitImageSmoothingEnabledno Chrome e Safari, e defini-los como false impedirá a ocorrência de anti-aliasing. Infelizmente, no momento em que escrevi, o IE9 e o Opera não implementaram essa propriedade, prefixada pelo fornecedor ou não.


Visualização: JSFiddle

Resultado:

insira a descrição da imagem aqui


1
Infelizmente, parece que isso também não funciona ainda. Talvez esteja faltando alguma coisa? Aqui está um teste: jsfiddle.net/VAXrL/187
namuol

12
você precisa escalar usando a API de tela para que funcione, nunca pode ser escalado com CSS e a API de tela afeta isso! Então, algo como isto: jsfiddle.net/VAXrL/190
Simon Sarris

1
Mas a natureza desse problema não é realmente sobre tela. É sobre como o navegador renderiza imagens em escala, incluindo elementos <canvas>.
namuol 4/09/12

4
Se você escalar usando CSS, a solução da namuol funcionará. Se você dimensionar a imagem manualmente no Canvas, a solução de Simon funcionará. É legal que haja soluções para os dois casos, então, graças a vocês dois!
Shaun Lebron

Para o IE 11: msImageSmoothingEnabled funciona para mim! msdn.microsoft.com/pt-br/library/dn265062(v=vs.85).aspx
steve

11

Editar 31/07/2012 - Esta funcionalidade está agora na especificação de tela! Veja a resposta em separado aqui:

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

A resposta antiga está abaixo para a posteridade.


Dependendo do efeito desejado, você tem isso como uma opção:

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/

O que cria isso:

insira a descrição da imagem aqui

Provavelmente não é o que você quer. Mas se você estava apenas procurando um borrão zero, esse seria o bilhete, então eu o oferecerei por precaução.

Uma opção mais difícil é usar a manipulação de pixels e escrever um algoritmo para o trabalho. Cada pixel da primeira imagem se torna um bloco 5x5 de pixels na nova imagem. Não seria muito difícil fazer com dados de imagem.

Mas o Canvas e o CSS por si só não o ajudarão aqui a escalar um para o outro com o efeito exato que você deseja.


Sim, era disso que eu tinha medo. Aparentemente, image-rendering: -webkit-optimize-contrastdeve fazer o truque, mas parece não fazer nada. Eu posso examinar uma função para aumentar o conteúdo de uma tela usando a interpolação do vizinho mais próximo.
namuol

Eu originalmente aceitei sua resposta; Eu o revoguei por enquanto, porque parece haver confusão sobre se isso é uma duplicata ou não.
Namuol #

Tenho uma resposta mais completa depois de fazer algumas pesquisas e gostaria de reabrir a pergunta para fornecer minha resposta.
Namhol #

3

No google chrome, os padrões de imagem da tela não são interpolados.

Aqui está um exemplo de trabalho editado a partir da resposta namuol http://jsfiddle.net/pGs4f/

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

Esta é a solução mais atraente que eu já vi até agora. Ainda não tenho certeza sobre o desempenho, mas vale a pena investigar. Vou testá-lo.
Namuol 12/05/12

usar repetição em vez de não repetição é mais rápido. Não sei porque. É também muito rápido, three.js usar isso em CanvasRenderer
saviski

6
update: não funciona mais com o Chrome 21 (devido ao apoio tela retina acrescentado?) nova versão jsfiddle.net/saviski/pGs4f/12 usando imageSmoothingEnabled apontado por Simão
saviski

Quando a resolução da Retina é geral, as coisas mudam enormemente. Quando a tela Retina de 326dpi (128dpcm), do estilo iPhone4-5, chega a monitores de 24 polegadas (52x32cm), o tamanho da tela será 6656 x 4096 px. O antialiasing é ruim e todos os fornecedores de navegadores (mesmo baseados no Webkit) são forçados a permitir a desativação do antialiasing. O antialiasing é uma operação intensiva da CPU e a antialiasing de 6656 x 4096 px seria muito lenta, mas felizmente desnecessária nessa exibição.
Timo Kähkönen 10/10/12

1
Assim como uma nota lateral, aqui está uma boa referência aboout padrões vs imagem: jsperf.com/drawimage-vs-canvaspattern/9
yckart

1

A solução alternativa de Saviski explicada aqui é promissora, porque funciona em:

  • Chrome 22.0.1229.79 Mac OS X 10.6.8
  • Chrome 22.0.1229.79 m Windows 7
  • Chromium 18.0.1025.168 (Compilação do Desenvolvedor 134367 Linux) Ubuntu 11.10
  • Firefox 3.6.25 Windows 7

Mas não funciona da seguinte maneira, mas o mesmo efeito pode ser alcançado usando a renderização de imagem CSS:

  • Firefox 15.0.1 Mac OS X 10.6.8 (renderização de imagem: -moz-crisp-edge funciona neste )
  • Opera 12.02 Mac OS X 10.6.8 (a renderização de imagem: -o-nítida-bordas funciona neste )
  • Opera 12.02 Windows 7 (a renderização de imagem: -o-crisp-edge funciona neste )

Os problemáticos são estes, porque ctx.XXXImageSmoothingEnabled não está funcionando e a renderização de imagem não está funcionando:

  • Safari 5.1.7 Mac OS X 10.6.8. (renderização de imagem: -webkit-optimize-contrast NÃO funciona)
  • Safari 5.1.7 Windows 7 (renderização de imagem: -webkit-optimize-contrast NÃO funciona)
  • IE 9 Windows 7 (-ms-interpolation-mode: o vizinho mais próximo NÃO funciona)
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.