Eu uso elementos de tela html5 para redimensionar imagens no meu navegador. Acontece que a qualidade é muito baixa. Encontrei o seguinte: Desativar interpolação ao dimensionar um <canvas> mas isso não ajuda a aumentar a qualidade.
Abaixo está o meu código css e js, bem como a imagem scalled com Photoshop e redimensionada na API do canvas.
O que devo fazer para obter a melhor qualidade ao dimensionar uma imagem no navegador?
Nota: Quero reduzir uma imagem grande para uma pequena, modificar a cor em uma tela e enviar o resultado da tela para o servidor.
CSS:
canvas, img {
image-rendering: optimizeQuality;
image-rendering: -moz-crisp-edges;
image-rendering: -webkit-optimize-contrast;
image-rendering: optimize-contrast;
-ms-interpolation-mode: nearest-neighbor;
}
JS:
var $img = $('<img>');
var $originalCanvas = $('<canvas>');
$img.load(function() {
var originalContext = $originalCanvas[0].getContext('2d');
originalContext.imageSmoothingEnabled = false;
originalContext.webkitImageSmoothingEnabled = false;
originalContext.mozImageSmoothingEnabled = false;
originalContext.drawImage(this, 0, 0, 379, 500);
});
A imagem foi redimensionada com o photoshop:
A imagem redimensionada na tela:
Editar:
Tentei fazer o downscaling em mais de uma etapa, conforme proposto em:
Redimensionando uma imagem em uma tela HTML5 e Html5 drawImage: como aplicar antialiasing
Esta é a função que eu usei:
function resizeCanvasImage(img, canvas, maxWidth, maxHeight) {
var imgWidth = img.width,
imgHeight = img.height;
var ratio = 1, ratio1 = 1, ratio2 = 1;
ratio1 = maxWidth / imgWidth;
ratio2 = maxHeight / imgHeight;
// Use the smallest ratio that the image best fit into the maxWidth x maxHeight box.
if (ratio1 < ratio2) {
ratio = ratio1;
}
else {
ratio = ratio2;
}
var canvasContext = canvas.getContext("2d");
var canvasCopy = document.createElement("canvas");
var copyContext = canvasCopy.getContext("2d");
var canvasCopy2 = document.createElement("canvas");
var copyContext2 = canvasCopy2.getContext("2d");
canvasCopy.width = imgWidth;
canvasCopy.height = imgHeight;
copyContext.drawImage(img, 0, 0);
// init
canvasCopy2.width = imgWidth;
canvasCopy2.height = imgHeight;
copyContext2.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvasCopy2.width, canvasCopy2.height);
var rounds = 2;
var roundRatio = ratio * rounds;
for (var i = 1; i <= rounds; i++) {
console.log("Step: "+i);
// tmp
canvasCopy.width = imgWidth * roundRatio / i;
canvasCopy.height = imgHeight * roundRatio / i;
copyContext.drawImage(canvasCopy2, 0, 0, canvasCopy2.width, canvasCopy2.height, 0, 0, canvasCopy.width, canvasCopy.height);
// copy back
canvasCopy2.width = imgWidth * roundRatio / i;
canvasCopy2.height = imgHeight * roundRatio / i;
copyContext2.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvasCopy2.width, canvasCopy2.height);
} // end for
// copy back to canvas
canvas.width = imgWidth * roundRatio / rounds;
canvas.height = imgHeight * roundRatio / rounds;
canvasContext.drawImage(canvasCopy2, 0, 0, canvasCopy2.width, canvasCopy2.height, 0, 0, canvas.width, canvas.height);
}
Aqui está o resultado se eu usar um dimensionamento em 2 etapas:
Aqui está o resultado se eu usar um dimensionamento em três etapas:
Aqui está o resultado se eu usar um dimensionamento em 4 etapas:
Aqui está o resultado se eu usar um dimensionamento de 20 etapas:
Nota: Acontece que de 1 a 2 etapas há uma grande melhoria na qualidade da imagem, mas quanto mais etapas você adiciona ao processo, mais difusa fica a imagem.
Existe uma maneira de resolver o problema em que a imagem fica mais confusa quanto mais etapas você adicionar?
Edit 04/10/2013: Eu tentei o algoritmo do GameAlchemist. Aqui está o resultado comparado ao Photoshop.
Imagem do PhotoShop:
Algoritmo do GameAlchemist:
fonte
Respostas:
Como seu problema é reduzir a escala da imagem, não há sentido em falar sobre interpolação, que é sobre a criação de pixels. O problema aqui é a redução da amostra.
Para reduzir a amostra de uma imagem, precisamos transformar cada quadrado de p * p pixels na imagem original em um único pixel na imagem de destino.
Por motivos de desempenho, os navegadores fazem uma redução de escala muito simples: para criar uma imagem menor, eles apenas escolhem UM pixel na fonte e usam seu valor para o destino. que 'esquece' alguns detalhes e adiciona ruído.
No entanto, há uma exceção: como a redução de tamanho da imagem 2X é muito simples de calcular (média de 4 pixels para criar um) e é usada para pixels retina / HiDPI, esse caso é tratado adequadamente - o navegador usa 4 pixels para fazer 1-.
MAS ... se você usar várias vezes uma downsampling 2X, enfrentará o problema de que os sucessivos erros de arredondamento adicionarão muito ruído.
O que é pior: você nem sempre será redimensionado com uma potência de dois e redimensionar para a potência mais próxima + um último redimensionamento é muito barulhento.
O que você procura é uma redução de tamanho de pixel perfeita, ou seja: uma nova amostragem da imagem que levará em consideração todos os pixels de entrada, independentemente da escala.
Para fazer isso, devemos calcular, para cada pixel de entrada, sua contribuição para um, dois ou quatro pixels de destino, dependendo de a projeção em escala dos pixels de entrada estar dentro dos pixels de destino, sobrepor uma borda X, Y ou ambas .
(Um esquema seria bom aqui, mas eu não tenho um.)
Aqui está um exemplo de escala de tela versus a escala perfeita de meu pixel em uma escala 1/3 de um zombat.
Observe que a imagem pode ser redimensionada no seu navegador e é .jpegized pelo SO.
No entanto, vemos que há muito menos barulho, especialmente na grama atrás do wombat e nos galhos à direita. O barulho no pêlo o torna mais contrastado, mas parece que ele tem cabelos brancos - uma imagem de fonte parecida -.
A imagem certa é menos cativante, mas definitivamente melhor.
Aqui está o código para fazer o downscaling perfeito de pixels:
resultado do violino: http://jsfiddle.net/gamealchemist/r6aVp/embedded/result/
próprio do violino: http://jsfiddle.net/gamealchemist/r6aVp/
É bastante ganancioso na memória, pois é necessário um buffer flutuante para armazenar os valores intermediários da imagem de destino (-> se contarmos a tela de resultado, usamos 6 vezes a memória da imagem de origem nesse algoritmo).
Também é bastante caro, já que cada pixel de origem é usado independentemente do tamanho do destino, e temos que pagar pelo getImageData / putImageDate, bem lento também.
Mas não há como ser mais rápido do que processar cada valor de origem nesse caso, e a situação não é tão ruim: para minha imagem de um wombat de 740 * 556, o processamento leva entre 30 e 40 ms.
fonte
Reamostragem rápida de tela com boa qualidade: http://jsfiddle.net/9g9Nv/442/
Atualização: versão 2.0 (mais rápido, trabalhadores da Web + objetos transferíveis) - https://github.com/viliusle/Hermite-resize
fonte
Sugestão 1 - estenda a tubulação do processo
Você pode usar o abandono conforme descrito nos links a que você se refere, mas parece usá-los de maneira errada.
Não é necessário reduzir o tamanho da imagem para proporções acima de 1: 2 (normalmente, mas não se limitando a). É onde você precisa fazer uma drástica redução de escala, é necessário dividi-la em duas (e raramente, mais) etapas, dependendo do conteúdo da imagem (em particular onde ocorrem altas frequências, como linhas finas).
Toda vez que você baixa uma amostra da imagem, perde detalhes e informações. Você não pode esperar que a imagem resultante seja tão nítida quanto a original.
Se você reduzir as imagens em várias etapas, perderá muitas informações no total e o resultado será ruim, como você já notou.
Tente com apenas um passo extra, ou no máximo dois.
Convoluções
No caso do Photoshop, observe que ele aplica uma convolução após a nova amostra da imagem, como a nitidez. Não é apenas a interpolação bicúbica que ocorre; para emular completamente o Photoshop, precisamos adicionar também as etapas que o Photoshop está executando (com a configuração padrão).
Neste exemplo, usarei minha resposta original a que você se refere em sua postagem, mas adicionei uma convolução mais nítida para melhorar a qualidade como um processo de postagem (consulte a demonstração na parte inferior).
Aqui está o código para adicionar filtro de nitidez (é baseado em um filtro de convolução genérico - eu coloquei a matriz de peso para nitidez dentro dela, bem como um fator de mistura para ajustar a pronúncia do efeito):
Uso:
o
mixFactor
valor é entre [0.0, 1.0] e permite subestimar o efeito de nitidez - regra geral: quanto menor o tamanho, menor é o efeito.Função (com base neste trecho ):
O resultado do uso dessa combinação será:
DEMO ONLINE AQUI
Dependendo da quantidade de nitidez que você deseja adicionar à mistura, é possível obter o resultado de "desfocado" padrão a muito nítido:
Sugestão 2 - implementação de algoritmo de baixo nível
Se você deseja obter o melhor resultado em termos de qualidade, precisará diminuir o nível e considerar a implementação, por exemplo, deste novo algoritmo para fazer isso.
Consulte Downsampling de imagem dependente de interpolação (2011) do IEEE.
Aqui está um link para o artigo na íntegra (PDF) .
No momento, não há implementações desse algoritmo no JavaScript AFAIK de JavaScript, portanto, você terá uma mão cheia se quiser se dedicar a essa tarefa.
A essência é (trechos do artigo):
Resumo
(consulte o link fornecido para todos os detalhes, fórmulas etc.)
fonte
Se você deseja usar apenas a tela, o melhor resultado será com várias etapas descendentes. Mas isso não é bom o suficiente ainda. Para uma melhor qualidade, você precisa de uma implementação js pura. Acabamos de lançar o pica - downscaler de alta velocidade com qualidade / velocidade variável. Em resumo, ele redimensiona 1280 * 1024px em ~ 0,1s e 5000 * 3000px em 1s, com a mais alta qualidade (filtro lanczos com 3 lobos). O Pica possui demonstração , onde você pode brincar com suas imagens, níveis de qualidade e até experimentá-lo em dispositivos móveis.
O Pica ainda não tem uma máscara de nitidez, mas isso será adicionado muito em breve. Isso é muito mais fácil do que implementar o filtro de convolução de alta velocidade para redimensionar.
fonte
Por que usar a tela para redimensionar imagens? Todos os navegadores modernos usam interpolação bicúbica - o mesmo processo usado pelo Photoshop (se você estiver fazendo certo) - e eles fazem isso mais rapidamente do que o processo de tela. Basta especificar o tamanho da imagem que você deseja (use apenas uma dimensão, altura ou largura, para redimensionar proporcionalmente).
Isso é suportado pela maioria dos navegadores, incluindo versões posteriores do IE. Versões anteriores podem exigir CSS específico do navegador .
Uma função simples (usando jQuery) para redimensionar uma imagem seria assim:
Em seguida, basta usar o valor retornado para redimensionar a imagem em uma ou ambas as dimensões.
Obviamente, existem diferentes refinamentos que você pode fazer, mas isso faz o trabalho.
Cole o seguinte código no console desta página e veja o que acontece com os gravatares:
fonte
Não é a resposta certa para pessoas que realmente precisam redimensionar a imagem, mas apenas para diminuir o tamanho do arquivo .
Eu tive um problema com as fotos "diretamente da câmera", que meus clientes frequentemente carregavam em JPEG "não compactado".
Não é tão conhecido que o canvas suporta (na maioria dos navegadores 2017) para alterar a qualidade do JPEG
Com esse truque, eu poderia reduzir fotos de 4k x 3k com> 10Mb para 1 ou 2Mb, com certeza depende de suas necessidades.
olhe aqui
fonte
Aqui está um serviço Angular reutilizável para redimensionamento de imagem / tela de alta qualidade: https://gist.github.com/fisch0920/37bac5e741eaec60e983
O serviço suporta a convolução de lanczos e o downscaling gradual. A abordagem de convolução é de qualidade mais alta, ao custo de ser mais lenta, enquanto a abordagem de redução de escala por etapas produz resultados razoavelmente antialias e é significativamente mais rápida.
Exemplo de uso:
fonte
Este é o filtro de redimensionamento Hermite aprimorado que utiliza 1 trabalhador para que a janela não congele.
https://github.com/calvintwr/blitz-hermite-resize
fonte
Encontrei uma solução que não precisa acessar diretamente os dados de pixel e percorrê-los para realizar a redução da amostragem. Dependendo do tamanho da imagem, isso pode consumir muitos recursos, e seria melhor usar os algoritmos internos do navegador.
A função drawImage () está usando um método de reamostragem de interpolação linear e vizinho mais próximo. Isso funciona bem quando você não está redimensionando mais da metade do tamanho original .
Se você fizer um loop para redimensionar no máximo metade de cada vez, os resultados serão muito bons e muito mais rápidos do que acessar dados de pixel.
Esta função reduz a amostra para metade de cada vez até atingir o tamanho desejado:
fonte
Talvez cara, você pode tentar isso, o que eu sempre uso no meu projeto. Dessa forma, você pode obter não apenas imagens de alta qualidade, mas qualquer outro elemento em sua tela.
fonte
em vez de 0,85 , se adicionarmos 1,0 . Você receberá uma resposta exata.
Você pode obter uma imagem clara e brilhante. por favor, verifique
fonte
Eu realmente tento evitar percorrer dados de imagem, especialmente em imagens maiores. Assim, criei uma maneira bastante simples de reduzir decentemente o tamanho da imagem sem restrições ou limitações, usando algumas etapas extras. Essa rotina desce para a metade mais baixa possível antes do tamanho desejado. Em seguida, ele aumenta o tamanho até o dobro do tamanho desejado e depois metade novamente. Parece engraçado no começo, mas os resultados são surpreendentemente bons e vão para lá rapidamente.
fonte
context.scale(xScale, yScale)
fonte
DEMO : Redimensionando imagens com o violinista JS e HTML Canvas Demo.
Você pode encontrar três métodos diferentes para redimensionar, que ajudarão a entender como o código está funcionando e por quê.
https://jsfiddle.net/1b68eLdr/93089/
O código completo da demonstração e o método TypeScript que você pode usar no seu código podem ser encontrados no projeto GitHub.
https://github.com/eyalc4/ts-image-resizer
Este é o código final:
fonte