Redimensionar imagem com tela javascript (suavemente)

90

Estou tentando redimensionar algumas imagens com tela, mas não tenho a menor ideia de como suavizá-las. No photoshop, navegadores, etc. existem alguns algoritmos que eles usam (por exemplo, bicúbico, bilinear), mas não sei se eles são integrados ao canvas ou não.

Aqui está meu violino: http://jsfiddle.net/EWupT/

var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
canvas.width=300
canvas.height=234
ctx.drawImage(img, 0, 0, 300, 234);
document.body.appendChild(canvas);

O primeiro é uma tag de imagem redimensionada normal e o segundo é uma tela. Observe como a tela não é tão lisa. Como posso obter 'suavidade'?

Steve
fonte

Respostas:

136

Você pode usar a redução para obter melhores resultados. A maioria dos navegadores parece usar interpolação linear em vez de bi-cúbica ao redimensionar imagens.

( Atualização foi adicionada uma propriedade de qualidade às especificações, imageSmoothingQualityque atualmente está disponível apenas no Chrome.)

A menos que não se escolha nenhuma suavização ou vizinho mais próximo, o navegador sempre interpola a imagem após redimensioná-la, pois esta função é um filtro passa-baixo para evitar o aliasing.

Bi-linear usa 2x2 pixels para fazer a interpolação enquanto bi-cúbico usa 4x4 então fazendo isso em etapas você pode chegar perto do resultado bi-cúbico enquanto usa interpolação bi-linear como visto nas imagens resultantes.

var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var img = new Image();

img.onload = function () {

    // set size proportional to image
    canvas.height = canvas.width * (img.height / img.width);

    // step 1 - resize to 50%
    var oc = document.createElement('canvas'),
        octx = oc.getContext('2d');

    oc.width = img.width * 0.5;
    oc.height = img.height * 0.5;
    octx.drawImage(img, 0, 0, oc.width, oc.height);

    // step 2
    octx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5);

    // step 3, resize to final size
    ctx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5,
    0, 0, canvas.width, canvas.height);
}
img.src = "//i.imgur.com/SHo6Fub.jpg";
<img src="//i.imgur.com/SHo6Fub.jpg" width="300" height="234">
<canvas id="canvas" width=300></canvas>

Dependendo de quão drástico é o seu redimensionamento, você pode pular a etapa 2 se a diferença for menor.

Na demonstração, você pode ver que o novo resultado agora é muito semelhante ao elemento de imagem.


fonte
1
@steve heh, às vezes essas coisas acontecem :) Para imagens, geralmente você pode substituir isso definindo um css BTW.
Ken, o primeiro resultado funcionou muito bem, mas quando eu mudo as imagens, você pode ver que está muito borrado jsfiddle.net/kcHLG O que pode ser feito neste caso e em outros?
steve,
@steve você pode reduzir o número de passos para apenas 1 ou nenhum (para algumas imagens isso funciona bem). Veja também esta resposta que é semelhante a esta, mas aqui eu adicionei uma convolução de nitidez para que você possa tornar a imagem resultante mais nítida depois de ser reduzida.
1
@steve aqui é um violino modificado com Bill usando apenas uma etapa extra: jsfiddle.net/AbdiasSoftware/kcHLG/1
1
@neaumusic o código é uma continuação do código OPs. Se você abrir o violino, verá ctx sendo definido. Descrevi aqui para evitar mal-entendidos.
27

Como o violino de Trung Le Nguyen Nhat não está correto (ele apenas usa a imagem original na última etapa)
, escrevi meu próprio violino geral com comparação de desempenho:

FIDDLE

Basicamente é:

img.onload = function() {
   var canvas = document.createElement('canvas'),
       ctx = canvas.getContext("2d"),
       oc = document.createElement('canvas'),
       octx = oc.getContext('2d');

   canvas.width = width; // destination canvas size
   canvas.height = canvas.width * img.height / img.width;

   var cur = {
     width: Math.floor(img.width * 0.5),
     height: Math.floor(img.height * 0.5)
   }

   oc.width = cur.width;
   oc.height = cur.height;

   octx.drawImage(img, 0, 0, cur.width, cur.height);

   while (cur.width * 0.5 > width) {
     cur = {
       width: Math.floor(cur.width * 0.5),
       height: Math.floor(cur.height * 0.5)
     };
     octx.drawImage(oc, 0, 0, cur.width * 2, cur.height * 2, 0, 0, cur.width, cur.height);
   }

   ctx.drawImage(oc, 0, 0, cur.width, cur.height, 0, 0, canvas.width, canvas.height);
}
Sebastian Ott
fonte
A resposta mais subestimada já vista.
Amsakanna,
17

Criei um serviço Angular reutilizável para lidar com redimensionamento de imagens / telas de alta qualidade para qualquer pessoa interessada: https://gist.github.com/transitive-bullshit/37bac5e741eaec60e983

O serviço inclui duas soluções porque ambas têm suas próprias vantagens / desvantagens. A abordagem de convolução de lanczos é de qualidade superior ao custo de ser mais lenta, enquanto a abordagem de redução gradual produz resultados razoavelmente suavizados e é significativamente mais rápida.

Exemplo de uso:

angular.module('demo').controller('ExampleCtrl', function (imageService) {
  // EXAMPLE USAGE
  // NOTE: it's bad practice to access the DOM inside a controller, 
  // but this is just to show the example usage.

  // resize by lanczos-sinc filter
  imageService.resize($('#myimg')[0], 256, 256)
    .then(function (resizedImage) {
      // do something with resized image
    })

  // resize by stepping down image size in increments of 2x
  imageService.resizeStep($('#myimg')[0], 256, 256)
    .then(function (resizedImage) {
      // do something with resized image
    })
})
fisch2
fonte
Desculpe por isso - eu tinha mudado meu nome de usuário do github. Acabei de atualizar o link gist.github.com/transitive-bullshit/37bac5e741eaec60e983
fisch2
3
Eu vi a palavra angular, tive aquela sensação engraçada
SuperUberDuper
8

Embora alguns desses trechos de código sejam curtos e funcionem, eles não são triviais de seguir e entender.

Como não sou um fã de "copiar e colar" do estouro de pilha, gostaria que os desenvolvedores entendessem o código que eles colocam no software, espero que você ache o seguinte útil.

DEMO : redimensionando imagens com violador JS e HTML Canvas Demo.

Você pode encontrar 3 métodos diferentes para fazer esse redimensionamento, que o 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 do método TypeScript que você pode usar em seu código pode ser encontrado no projeto GitHub.

https://github.com/eyalc4/ts-image-resizer

Este é o código final:

export class ImageTools {
base64ResizedImage: string = null;

constructor() {
}

ResizeImage(base64image: string, width: number = 1080, height: number = 1080) {
    let img = new Image();
    img.src = base64image;

    img.onload = () => {

        // Check if the image require resize at all
        if(img.height <= height && img.width <= width) {
            this.base64ResizedImage = base64image;

            // TODO: Call method to do something with the resize image
        }
        else {
            // Make sure the width and height preserve the original aspect ratio and adjust if needed
            if(img.height > img.width) {
                width = Math.floor(height * (img.width / img.height));
            }
            else {
                height = Math.floor(width * (img.height / img.width));
            }

            let resizingCanvas: HTMLCanvasElement = document.createElement('canvas');
            let resizingCanvasContext = resizingCanvas.getContext("2d");

            // Start with original image size
            resizingCanvas.width = img.width;
            resizingCanvas.height = img.height;


            // Draw the original image on the (temp) resizing canvas
            resizingCanvasContext.drawImage(img, 0, 0, resizingCanvas.width, resizingCanvas.height);

            let curImageDimensions = {
                width: Math.floor(img.width),
                height: Math.floor(img.height)
            };

            let halfImageDimensions = {
                width: null,
                height: null
            };

            // Quickly reduce the dize by 50% each time in few iterations until the size is less then
            // 2x time the target size - the motivation for it, is to reduce the aliasing that would have been
            // created with direct reduction of very big image to small image
            while (curImageDimensions.width * 0.5 > width) {
                // Reduce the resizing canvas by half and refresh the image
                halfImageDimensions.width = Math.floor(curImageDimensions.width * 0.5);
                halfImageDimensions.height = Math.floor(curImageDimensions.height * 0.5);

                resizingCanvasContext.drawImage(resizingCanvas, 0, 0, curImageDimensions.width, curImageDimensions.height,
                    0, 0, halfImageDimensions.width, halfImageDimensions.height);

                curImageDimensions.width = halfImageDimensions.width;
                curImageDimensions.height = halfImageDimensions.height;
            }

            // Now do final resize for the resizingCanvas to meet the dimension requirments
            // directly to the output canvas, that will output the final image
            let outputCanvas: HTMLCanvasElement = document.createElement('canvas');
            let outputCanvasContext = outputCanvas.getContext("2d");

            outputCanvas.width = width;
            outputCanvas.height = height;

            outputCanvasContext.drawImage(resizingCanvas, 0, 0, curImageDimensions.width, curImageDimensions.height,
                0, 0, width, height);

            // output the canvas pixels as an image. params: format, quality
            this.base64ResizedImage = outputCanvas.toDataURL('image/jpeg', 0.85);

            // TODO: Call method to do something with the resize image
        }
    };
}}
Eyal c
fonte
4

Eu criei uma biblioteca que permite reduzir qualquer porcentagem enquanto mantém todos os dados de cores.

https://github.com/danschumann/limby-resize/blob/master/lib/canvas_resize.js

Esse arquivo você pode incluir no navegador. Os resultados serão parecidos com photoshop ou image magick, preservando todos os dados de cores, fazendo a média dos pixels, ao invés de pegar os próximos e descartar outros. Ele não usa uma fórmula para adivinhar as médias, mas sim a média exata.

Funkodebat
fonte
1
Provavelmente usaria webgl para redimensionar agora
Funkodebat
4

Com base na resposta K3N, eu reescrevo o código geralmente para quem quiser

var oc = document.createElement('canvas'), octx = oc.getContext('2d');
    oc.width = img.width;
    oc.height = img.height;
    octx.drawImage(img, 0, 0);
    while (oc.width * 0.5 > width) {
       oc.width *= 0.5;
       oc.height *= 0.5;
       octx.drawImage(oc, 0, 0, oc.width, oc.height);
    }
    oc.width = width;
    oc.height = oc.width * img.height / img.width;
    octx.drawImage(img, 0, 0, oc.width, oc.height);

ATUALIZAR DEMO JSFIDDLE

Aqui está minha DEMO ONLINE

Trung Le Nguyen Nhat
fonte
2
Isso não funcionará: cada vez que você redimensionar a tela, seu contexto será limpo. Você precisa de 2 telas. Aqui é exatamente o mesmo que chamar drawImage diretamente com as dimensões finais.
Kaiido
2

Não entendo por que ninguém está sugerindo createImageBitmap.

createImageBitmap(
    document.getElementById('image'), 
    { resizeWidth: 300, resizeHeight: 234, resizeQuality: 'high' }
)
.then(imageBitmap => 
    document.getElementById('canvas').getContext('2d').drawImage(imageBitmap, 0, 0)
);

funciona perfeitamente (assumindo que você definiu ids para imagem e tela).

cagdas_ucar
fonte
Porque não é amplamente suportado caniuse.com/#search=createImageBitmap
Matt
createImageBitmap é compatível com 73% de todos os usuários. Dependendo do seu caso de uso, pode ser bom o suficiente. É apenas o Safari que se recusa a adicionar suporte para ele. Acho que vale a pena mencionar como uma possível solução.
cagdas_ucar
Boa solução, mas infelizmente não funciona no firefox
vcarel
1

Eu escrevi um pequeno utilitário js para cortar e redimensionar a imagem no front-end. Aqui está o link do projeto GitHub. Além disso, você pode obter o blob da imagem final para enviá-lo.

import imageSqResizer from './image-square-resizer.js'

let resizer = new imageSqResizer(
    'image-input',
    300,
    (dataUrl) => 
        document.getElementById('image-output').src = dataUrl;
);
//Get blob
let formData = new FormData();
formData.append('files[0]', resizer.blob);

//get dataUrl
document.getElementById('image-output').src = resizer.dataUrl;
Diyaz Yakubov
fonte