Redimensionando uma imagem em uma tela HTML5

314

Estou tentando criar uma imagem em miniatura no lado do cliente usando javascript e um elemento de tela, mas quando diminuo a imagem, ela parece terrível. Parece que foi reduzido no photoshop com a reamostragem definida como 'Vizinho mais próximo' em vez de Bicubic. Eu sei que é possível fazer com que isso pareça certo, porque este site pode fazê-lo bem usando uma tela também. Eu tentei usar o mesmo código que eles fazem, como mostrado no link "[Source]", mas ainda parece terrível. Há algo que estou faltando, alguma configuração que precisa ser definida ou algo assim?

EDITAR:

Estou tentando redimensionar um jpg. Eu tentei redimensionar o mesmo jpg no site vinculado e no photoshop, e parece bom quando reduzido.

Aqui está o código relevante:

reader.onloadend = function(e)
{
    var img = new Image();
    var ctx = canvas.getContext("2d");
    var canvasCopy = document.createElement("canvas");
    var copyContext = canvasCopy.getContext("2d");

    img.onload = function()
    {
        var ratio = 1;

        if(img.width > maxWidth)
            ratio = maxWidth / img.width;
        else if(img.height > maxHeight)
            ratio = maxHeight / img.height;

        canvasCopy.width = img.width;
        canvasCopy.height = img.height;
        copyContext.drawImage(img, 0, 0);

        canvas.width = img.width * ratio;
        canvas.height = img.height * ratio;
        ctx.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvas.width, canvas.height);
    };

    img.src = reader.result;
}

EDIT2:

Parece que eu estava enganado, o site vinculado não estava melhorando o trabalho de reduzir o tamanho da imagem. Eu tentei os outros métodos sugeridos e nenhum deles parece melhor. É nisso que os diferentes métodos resultaram:

Photoshop:

texto alternativo

Tela de pintura:

texto alternativo

Imagem com renderização de imagem: optimizeQuality definido e dimensionado com largura / altura:

texto alternativo

Imagem com renderização de imagem: optimizeQuality definido e dimensionado com -moz-transform:

texto alternativo

Redimensionamento de tela no pixastic:

texto alternativo

Eu acho que isso significa que o Firefox não está usando amostragem bicúbica como deveria. Vou ter que esperar até que eles realmente o adicionem.

EDIT3:

Imagem original

Telanor
fonte
Você pode postar o código que está usando para redimensionar a imagem?
Xavi
Você está tentando redimensionar uma imagem GIF ou similar com uma paleta limitada? Mesmo no photoshop, essas imagens não diminuem bem a menos que você as converta em RGB.
leepowers
Você pode postar uma cópia da imagem original?
Xavi
Redimensionar a imagem usando javascript é um pouco complicado - você não apenas está usando o poder de processamento do cliente para redimensionar a imagem, mas está fazendo isso em cada carregamento de página. Por que não salvar uma versão reduzida do photoshop e servi-la / em conjunto com a imagem original?
define
29
Porque estou criando um carregador de imagens com a capacidade de redimensionar e cortar as imagens antes de enviá-las.
Telanor 8/03/10

Respostas:

393

Então, o que você faz se todos os navegadores (na verdade, o Chrome 5 me deram um bom) não lhe forneceram uma qualidade de reamostragem suficientemente boa? Você os implementa você mesmo então! Ah, vamos lá, estamos entrando na nova era da Web 3.0, navegadores compatíveis com HTML5, compiladores jIT Javascript super otimizados, máquinas com vários núcleos (†), com toneladas de memória, do que você tem medo? Ei, tem a palavra java em javascript, para garantir o desempenho, certo? Eis o código gerador de miniaturas:

// returns a function that calculates lanczos weight
function lanczosCreate(lobes) {
    return function(x) {
        if (x > lobes)
            return 0;
        x *= Math.PI;
        if (Math.abs(x) < 1e-16)
            return 1;
        var xx = x / lobes;
        return Math.sin(x) * Math.sin(xx) / x / xx;
    };
}

// elem: canvas element, img: image element, sx: scaled width, lobes: kernel radius
function thumbnailer(elem, img, sx, lobes) {
    this.canvas = elem;
    elem.width = img.width;
    elem.height = img.height;
    elem.style.display = "none";
    this.ctx = elem.getContext("2d");
    this.ctx.drawImage(img, 0, 0);
    this.img = img;
    this.src = this.ctx.getImageData(0, 0, img.width, img.height);
    this.dest = {
        width : sx,
        height : Math.round(img.height * sx / img.width),
    };
    this.dest.data = new Array(this.dest.width * this.dest.height * 3);
    this.lanczos = lanczosCreate(lobes);
    this.ratio = img.width / sx;
    this.rcp_ratio = 2 / this.ratio;
    this.range2 = Math.ceil(this.ratio * lobes / 2);
    this.cacheLanc = {};
    this.center = {};
    this.icenter = {};
    setTimeout(this.process1, 0, this, 0);
}

thumbnailer.prototype.process1 = function(self, u) {
    self.center.x = (u + 0.5) * self.ratio;
    self.icenter.x = Math.floor(self.center.x);
    for (var v = 0; v < self.dest.height; v++) {
        self.center.y = (v + 0.5) * self.ratio;
        self.icenter.y = Math.floor(self.center.y);
        var a, r, g, b;
        a = r = g = b = 0;
        for (var i = self.icenter.x - self.range2; i <= self.icenter.x + self.range2; i++) {
            if (i < 0 || i >= self.src.width)
                continue;
            var f_x = Math.floor(1000 * Math.abs(i - self.center.x));
            if (!self.cacheLanc[f_x])
                self.cacheLanc[f_x] = {};
            for (var j = self.icenter.y - self.range2; j <= self.icenter.y + self.range2; j++) {
                if (j < 0 || j >= self.src.height)
                    continue;
                var f_y = Math.floor(1000 * Math.abs(j - self.center.y));
                if (self.cacheLanc[f_x][f_y] == undefined)
                    self.cacheLanc[f_x][f_y] = self.lanczos(Math.sqrt(Math.pow(f_x * self.rcp_ratio, 2)
                            + Math.pow(f_y * self.rcp_ratio, 2)) / 1000);
                weight = self.cacheLanc[f_x][f_y];
                if (weight > 0) {
                    var idx = (j * self.src.width + i) * 4;
                    a += weight;
                    r += weight * self.src.data[idx];
                    g += weight * self.src.data[idx + 1];
                    b += weight * self.src.data[idx + 2];
                }
            }
        }
        var idx = (v * self.dest.width + u) * 3;
        self.dest.data[idx] = r / a;
        self.dest.data[idx + 1] = g / a;
        self.dest.data[idx + 2] = b / a;
    }

    if (++u < self.dest.width)
        setTimeout(self.process1, 0, self, u);
    else
        setTimeout(self.process2, 0, self);
};
thumbnailer.prototype.process2 = function(self) {
    self.canvas.width = self.dest.width;
    self.canvas.height = self.dest.height;
    self.ctx.drawImage(self.img, 0, 0, self.dest.width, self.dest.height);
    self.src = self.ctx.getImageData(0, 0, self.dest.width, self.dest.height);
    var idx, idx2;
    for (var i = 0; i < self.dest.width; i++) {
        for (var j = 0; j < self.dest.height; j++) {
            idx = (j * self.dest.width + i) * 3;
            idx2 = (j * self.dest.width + i) * 4;
            self.src.data[idx2] = self.dest.data[idx];
            self.src.data[idx2 + 1] = self.dest.data[idx + 1];
            self.src.data[idx2 + 2] = self.dest.data[idx + 2];
        }
    }
    self.ctx.putImageData(self.src, 0, 0);
    self.canvas.style.display = "block";
};

... com os quais você pode produzir resultados como estes!

img717.imageshack.us/img717/8910/lanczos358.png

de qualquer maneira, aqui está uma versão 'fixa' do seu exemplo:

img.onload = function() {
    var canvas = document.createElement("canvas");
    new thumbnailer(canvas, img, 188, 3); //this produces lanczos3
    // but feel free to raise it up to 8. Your client will appreciate
    // that the program makes full use of his machine.
    document.body.appendChild(canvas);
};

Agora é hora de colocar seus melhores navegadores por aí e ver qual deles provavelmente aumentará a pressão sanguínea do seu cliente!

Umm, onde está minha etiqueta de sarcasmo?

(como muitas partes do código são baseadas no Anrieff Gallery Generator, ele também é coberto pela GPL2? Não sei)

devido à limitação do javascript, o multi-core não é suportado.

syockit
fonte
2
Na verdade, eu tentei implementá-lo, fazendo o que você fez, copiando o código de um editor de imagens de código aberto. Como não consegui encontrar nenhuma documentação sólida sobre o algoritmo, tive dificuldades para otimizá-lo. No final, o meu estava meio lento (levou alguns segundos para redimensionar a imagem). Quando tiver a chance, vou experimentar a sua e ver se é mais rápida. E acho que os trabalhadores da web possibilitam o javascript multi-core agora. Eu ia tentar usá-los para acelerá-lo, mas eu estava tendo dificuldade para descobrir como fazer isso em um algoritmo de vários segmentos
Telanor
3
Desculpe, esqueci isso! Eu editei a resposta. De qualquer forma, não será rápido, o bicúbico deve ser mais rápido. Sem mencionar que o algoritmo que usei não é o redimensionamento bidirecional usual (que é linha por linha, horizontal e vertical), por isso é mais lento.
syockit 16/07/10
5
Você é incrível e merece toneladas de grandiosidade.
Rocklan 02/02
5
Isso produz resultados decentes, mas leva 7,4 segundos para uma imagem de 1.8 MP na versão mais recente do Chrome ...
MPEN
2
Como métodos como esse conseguem uma pontuação tão alta? A solução mostrada falha ao considerar a escala logarítmica usada para armazenar informações de cores. Um RGB de 127,127,127 representa um quarto do brilho de 255, 255, 255 e não a metade. A amostragem descendente na solução resulta em uma imagem escura. Vergonha esta está fechada porque não há um método muito simples e rápido de tamanho para baixo que produz resultados ainda melhores do que o Photoshop (OP deve ter tido as preferências definida errado) da amostra dado
Blindman67
37

Algoritmo rápido de redimensionamento / nova amostra de imagem usando o filtro Hermite com JavaScript. Suporte transparência, dá boa qualidade. Pré-visualização:

insira a descrição da imagem aqui

Atualização : versão 2.0 adicionada no GitHub (mais rápido, web workers + objetos transferíveis). Finalmente eu consegui trabalhar!

Git: https://github.com/viliusle/Hermite-resize
Demo: http://viliusle.github.io/miniPaint/

/**
 * Hermite resize - fast image resize/resample using Hermite filter. 1 cpu version!
 * 
 * @param {HtmlElement} canvas
 * @param {int} width
 * @param {int} height
 * @param {boolean} resize_canvas if true, canvas will be resized. Optional.
 */
function resample_single(canvas, width, height, resize_canvas) {
    var width_source = canvas.width;
    var height_source = canvas.height;
    width = Math.round(width);
    height = Math.round(height);

    var ratio_w = width_source / width;
    var ratio_h = height_source / height;
    var ratio_w_half = Math.ceil(ratio_w / 2);
    var ratio_h_half = Math.ceil(ratio_h / 2);

    var ctx = canvas.getContext("2d");
    var img = ctx.getImageData(0, 0, width_source, height_source);
    var img2 = ctx.createImageData(width, height);
    var data = img.data;
    var data2 = img2.data;

    for (var j = 0; j < height; j++) {
        for (var i = 0; i < width; i++) {
            var x2 = (i + j * width) * 4;
            var weight = 0;
            var weights = 0;
            var weights_alpha = 0;
            var gx_r = 0;
            var gx_g = 0;
            var gx_b = 0;
            var gx_a = 0;
            var center_y = (j + 0.5) * ratio_h;
            var yy_start = Math.floor(j * ratio_h);
            var yy_stop = Math.ceil((j + 1) * ratio_h);
            for (var yy = yy_start; yy < yy_stop; yy++) {
                var dy = Math.abs(center_y - (yy + 0.5)) / ratio_h_half;
                var center_x = (i + 0.5) * ratio_w;
                var w0 = dy * dy; //pre-calc part of w
                var xx_start = Math.floor(i * ratio_w);
                var xx_stop = Math.ceil((i + 1) * ratio_w);
                for (var xx = xx_start; xx < xx_stop; xx++) {
                    var dx = Math.abs(center_x - (xx + 0.5)) / ratio_w_half;
                    var w = Math.sqrt(w0 + dx * dx);
                    if (w >= 1) {
                        //pixel too far
                        continue;
                    }
                    //hermite filter
                    weight = 2 * w * w * w - 3 * w * w + 1;
                    var pos_x = 4 * (xx + yy * width_source);
                    //alpha
                    gx_a += weight * data[pos_x + 3];
                    weights_alpha += weight;
                    //colors
                    if (data[pos_x + 3] < 255)
                        weight = weight * data[pos_x + 3] / 250;
                    gx_r += weight * data[pos_x];
                    gx_g += weight * data[pos_x + 1];
                    gx_b += weight * data[pos_x + 2];
                    weights += weight;
                }
            }
            data2[x2] = gx_r / weights;
            data2[x2 + 1] = gx_g / weights;
            data2[x2 + 2] = gx_b / weights;
            data2[x2 + 3] = gx_a / weights_alpha;
        }
    }
    //clear and resize canvas
    if (resize_canvas === true) {
        canvas.width = width;
        canvas.height = height;
    } else {
        ctx.clearRect(0, 0, width_source, height_source);
    }

    //draw
    ctx.putImageData(img2, 0, 0);
}
ViliusL
fonte
Talvez você possa incluir links para sua demonstração do miniPaint e o repositório do Github?
syockit 11/09/13
1
Você também compartilhará a versão dos webworkers? Provavelmente devido à sobrecarga da configuração, é mais lento para imagens pequenas, mas pode ser útil para imagens de origem maiores.
syockit 11/09/13
demo adicionada, links git, também versão multi-core. Btw eu não gastei muito tempo na otimização da versão multicore ... Versão única, acredito que é otimizada também.
ViliusL
Diferença enorme e desempenho decente. Muito obrigado! antes e depois
KevBurnsJr
2
@ViliusL Ah, agora me lembrei por que os trabalhadores da Web não funcionavam tão bem. Eles não tinham memória compartilhada antes e ainda não a têm agora! Talvez um dia, quando eles conseguem resolver o problema, o código virá para uso (que, ou talvez as pessoas usam PNaCl vez)
syockit
26

Experimente pica - é um redimensionador altamente otimizado com algoritmos selecionáveis. Veja a demonstração .

Por exemplo, a imagem original da primeira postagem é redimensionada em 120ms com filtro Lanczos e janela de 3px ou 60ms com filtro de caixa e janela de 0,5px. Para uma enorme imagem de 17mb, o redimensionamento de 5000x3000px leva ~ 1s no computador e 3s no celular.

Todos os princípios de redimensionamento foram descritos muito bem neste tópico, e o pica não adiciona ciência de foguetes. Mas é otimizado muito bem para os JIT-s modernos e está pronto para uso imediato (via npm ou bower). Além disso, ele usa trabalhadores da Web, quando disponíveis, para evitar congelamentos na interface.

Também pretendo adicionar o suporte à máscara de nitidez em breve, porque é muito útil após o downscale.

Vitaly
fonte
14

Eu sei que esse é um tópico antigo, mas pode ser útil para algumas pessoas, como eu, que meses depois estão atingindo esse problema pela primeira vez.

Aqui está um código que redimensiona a imagem toda vez que você a recarrega. Estou ciente de que isso não é ideal, mas forneço isso como uma prova de conceito.

Além disso, desculpe-me por usar o jQuery para seletores simples, mas me sinto muito à vontade com a sintaxe.

$(document).on('ready', createImage);
$(window).on('resize', createImage);

var createImage = function(){
  var canvas = document.getElementById('myCanvas');
  canvas.width = window.innerWidth || $(window).width();
  canvas.height = window.innerHeight || $(window).height();
  var ctx = canvas.getContext('2d');
  img = new Image();
  img.addEventListener('load', function () {
    ctx.drawImage(this, 0, 0, w, h);
  });
  img.src = 'http://www.ruinvalor.com/Telanor/images/original.jpg';
};
html, body{
  height: 100%;
  width: 100%;
  margin: 0;
  padding: 0;
  background: #000;
}
canvas{
  position: absolute;
  left: 0;
  top: 0;
  z-index: 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Canvas Resize</title>
  </head>
  <body>
    <canvas id="myCanvas"></canvas>
  </body>
</html>

Minha função createImage é chamada uma vez quando o documento é carregado e depois é chamada toda vez que a janela recebe um evento de redimensionamento.

Eu testei no Chrome 6 e Firefox 3.6, ambos no Mac. Essa "técnica" consome o processador como se fosse sorvete no verão, mas funciona.

cesarsalazar
fonte
9

Eu coloquei alguns algoritmos para fazer a interpolação de imagens em matrizes de pixels de tela html que podem ser úteis aqui:

https://web.archive.org/web/20170104190425/http://jsperf.com:80/pixel-interpolation/2

Elas podem ser copiadas / coladas e podem ser usadas dentro dos profissionais da Web para redimensionar imagens (ou qualquer outra operação que exija interpolação - estou usando-as para desafiar imagens no momento).

Eu não adicionei o material de lanczos acima, então fique à vontade para adicioná-lo como uma comparação, se desejar.

Daniel
fonte
6

Esta é uma função javascript adaptada do código da @ Telanor. Ao passar uma imagem base64 como primeiro argumento para a função, ela retorna a base64 da imagem redimensionada. maxWidth e maxHeight são opcionais.

function thumbnail(base64, maxWidth, maxHeight) {

  // Max size for thumbnail
  if(typeof(maxWidth) === 'undefined') var maxWidth = 500;
  if(typeof(maxHeight) === 'undefined') var maxHeight = 500;

  // Create and initialize two canvas
  var canvas = document.createElement("canvas");
  var ctx = canvas.getContext("2d");
  var canvasCopy = document.createElement("canvas");
  var copyContext = canvasCopy.getContext("2d");

  // Create original image
  var img = new Image();
  img.src = base64;

  // Determine new ratio based on max size
  var ratio = 1;
  if(img.width > maxWidth)
    ratio = maxWidth / img.width;
  else if(img.height > maxHeight)
    ratio = maxHeight / img.height;

  // Draw original image in second canvas
  canvasCopy.width = img.width;
  canvasCopy.height = img.height;
  copyContext.drawImage(img, 0, 0);

  // Copy and resize second canvas to first canvas
  canvas.width = img.width * ratio;
  canvas.height = img.height * ratio;
  ctx.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvas.width, canvas.height);

  return canvas.toDataURL();

}
Christophe Marois
fonte
sua abordagem é muito rápido, mas produz uma imagem difusa como você pode ver aqui: stackoverflow.com/questions/18922880/...
confile
6

Eu sugiro que você verifique este link e verifique se está definido como verdadeiro.

Controlando o comportamento da escala de imagem

Introduzido no Gecko 1.9.2 (Firefox 3.6 / Thunderbird 3.1 / Fennec 1.0)

O Gecko 1.9.2 introduziu a propriedade mozImageSmoothingEnabled no elemento canvas; se esse valor booleano for falso, as imagens não serão suavizadas quando dimensionadas. Esta propriedade é verdadeira por padrão. ver impressão simples?

  1. cx.mozImageSmoothingEnabled = false;
Evan Carroll
fonte
5

Se você está simplesmente tentando redimensionar uma imagem, eu recomendo a configuração widthe heighta imagem com CSS. Aqui está um exemplo rápido:

.small-image {
    width: 100px;
    height: 100px;
}

Observe que o heighte widthtambém pode ser definido usando JavaScript. Aqui está um exemplo de código rápido:

var img = document.getElement("my-image");
img.style.width = 100 + "px";  // Make sure you add the "px" to the end,
img.style.height = 100 + "px"; // otherwise you'll confuse IE

Além disso, para garantir que a imagem redimensionada tenha boa aparência, adicione as seguintes regras de css ao seletor de imagens:

Pelo que sei, todos os navegadores, exceto o IE, usam um algoritmo bicúbico para redimensionar imagens por padrão, para que suas imagens redimensionadas fiquem bem no Firefox e Chrome.

Se definir o css widthe heightnão funcionar, convém jogar com um css transform:

Se, por qualquer motivo, você precisar usar uma tela, observe que há duas maneiras de redimensionar uma imagem: redimensionando a tela com css ou desenhando a imagem em um tamanho menor.

Veja esta pergunta para mais detalhes.

Xavi
fonte
5
Infelizmente, nem redimensionar a tela nem desenhar a imagem em um tamanho menor resolve o problema (no Chrome).
Nestor
1
O Chrome 27 produz uma boa imagem redimensionada, mas você não pode copiar o resultado em uma tela; tentar fazer isso copiará a imagem original.
syockit
4

Para redimensionar a imagem com largura menor que o original, eu uso:

    function resize2(i) {
      var cc = document.createElement("canvas");
      cc.width = i.width / 2;
      cc.height = i.height / 2;
      var ctx = cc.getContext("2d");
      ctx.drawImage(i, 0, 0, cc.width, cc.height);
      return cc;
    }
    var cc = img;
    while (cc.width > 64 * 2) {
      cc = resize2(cc);
    }
    // .. than drawImage(cc, .... )

e funciona =).

Yaffle
fonte
4

Eu obtive essa imagem clicando com o botão direito do mouse no elemento canvas no firefox e salvando como.

texto alternativo

var img = new Image();
img.onload = function () {
    console.debug(this.width,this.height);
    var canvas = document.createElement('canvas'), ctx;
    canvas.width = 188;
    canvas.height = 150;
    document.body.appendChild(canvas);
    ctx = canvas.getContext('2d');
    ctx.drawImage(img,0,0,188,150);
};
img.src = 'original.jpg';

de qualquer maneira, aqui está uma versão 'fixa' do seu exemplo:

var img = new Image();
// added cause it wasnt defined
var canvas = document.createElement("canvas");
document.body.appendChild(canvas);

var ctx = canvas.getContext("2d");
var canvasCopy = document.createElement("canvas");
// adding it to the body

document.body.appendChild(canvasCopy);

var copyContext = canvasCopy.getContext("2d");

img.onload = function()
{
        var ratio = 1;

        // defining cause it wasnt
        var maxWidth = 188,
            maxHeight = 150;

        if(img.width > maxWidth)
                ratio = maxWidth / img.width;
        else if(img.height > maxHeight)
                ratio = maxHeight / img.height;

        canvasCopy.width = img.width;
        canvasCopy.height = img.height;
        copyContext.drawImage(img, 0, 0);

        canvas.width = img.width * ratio;
        canvas.height = img.height * ratio;
        // the line to change
        // ctx.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvas.width, canvas.height);
        // the method signature you are using is for slicing
        ctx.drawImage(canvasCopy, 0, 0, canvas.width, canvas.height);
};

// changed for example
img.src = 'original.jpg';
Robert
fonte
Eu tentei fazer o que você fez e não sai bem como o seu. A menos que eu tenha perdido alguma coisa, a única alteração que você fez foi usar a assinatura do método de dimensionamento em vez da assinatura de fatias, certo? Por alguma razão, não está funcionando para mim.
Telanor 14/03/10
3

O problema com algumas dessas soluções é que elas acessam diretamente os dados de pixel e fazem um loop através deles para realizar a redução de 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:

  function resize_image( src, dst, type, quality ) {
     var tmp = new Image(),
         canvas, context, cW, cH;

     type = type || 'image/jpeg';
     quality = quality || 0.92;

     cW = src.naturalWidth;
     cH = src.naturalHeight;

     tmp.src = src.src;
     tmp.onload = function() {

        canvas = document.createElement( 'canvas' );

        cW /= 2;
        cH /= 2;

        if ( cW < src.width ) cW = src.width;
        if ( cH < src.height ) cH = src.height;

        canvas.width = cW;
        canvas.height = cH;
        context = canvas.getContext( '2d' );
        context.drawImage( tmp, 0, 0, cW, cH );

        dst.src = canvas.toDataURL( type, quality );

        if ( cW <= src.width || cH <= src.height )
           return;

        tmp.src = dst.src;
     }

  }
  // The images sent as parameters can be in the DOM or be image objects
  resize_image( $( '#original' )[0], $( '#smaller' )[0] );

Créditos para este post

Jesús Carrera
fonte
2

Então, algo interessante que encontrei há algum tempo enquanto trabalhava com telas que pode ser útil:

Para redimensionar o controle de tela por si só, você precisa usar os atributos height=""e width=""(ou canvas.width/ canvas.heightelementos). Se você usar CSS para redimensionar a tela, ela realmente esticará (ou seja: redimensionará) o conteúdo da tela para caber na tela inteira (em vez de simplesmente aumentar ou diminuir a área da tela).

Vale a pena tentar desenhar a imagem em um controle de tela com os atributos de altura e largura definidos para o tamanho da imagem e, em seguida, usar CSS para redimensionar a tela para o tamanho que você está procurando. Talvez isso usasse um algoritmo de redimensionamento diferente.

Também deve ser observado que a tela tem efeitos diferentes em navegadores diferentes (e até versões diferentes de navegadores diferentes). É provável que os algoritmos e as técnicas usadas nos navegadores mudem com o tempo (especialmente com o Firefox 4 e o Chrome 6 sendo lançados tão cedo, o que dará muita ênfase ao desempenho da renderização de telas).

Além disso, você também pode tentar o SVG, pois provavelmente também usa um algoritmo diferente.

Boa sorte!

mattbasta
fonte
1
Definir a largura ou a altura de uma tela através dos atributos HTML faz com que a tela seja limpa, portanto não é possível redimensionar esse método. Além disso, o SVG destina-se a lidar com imagens matemáticas. Eu preciso ser capaz de desenhar PNGs e coisas assim, para que não me ajudem por aí.
Telanor 6/07/10
Definir a altura e largura da tela e redimensionar usando CSS não ajuda, descobri (no Chrome). Mesmo fazendo o redimensionamento usando -webkit-transform em vez de CSS width / height não obtém a interpolação.
Nestor
2

Sinto que o módulo que escrevi produzirá resultados semelhantes ao photoshop, pois preserva os dados de cores calculando a média deles, sem aplicar um algoritmo. É meio lento, mas para mim é o melhor, porque preserva todos os dados de cores.

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

Ele não pega o vizinho mais próximo e descarta outros pixels, ou experimenta um grupo e mede uma média aleatória. É preciso a proporção exata que cada pixel de origem deve gerar no pixel de destino. A cor média do pixel na fonte será a cor média do pixel no destino, o que essas outras fórmulas, acho que não serão.

um exemplo de como usar está na parte inferior de https://github.com/danschumann/limby-resize

ATUALIZAÇÃO OUT 2018 : Atualmente, meu exemplo é mais acadêmico do que qualquer outra coisa. O Webgl é praticamente 100%, então é melhor redimensioná-lo para produzir resultados semelhantes, mas mais rápido. PICA.js faz isso, acredito. -

Funkodebat
fonte
1

Converti a resposta do @ syockit e a abordagem de redução em um serviço Angular reutilizável para quem estiver interessado: https://gist.github.com/fisch0920/37bac5e741eaec60e983

Incluí as duas soluções porque ambas têm seus próprios prós / contras. A abordagem de convolução de lanczos é de qualidade mais alta, ao custo de ser mais lenta, enquanto a abordagem de downscaling gradual produz resultados razoavelmente antialias 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
1

Redimensionador de imagem Javascript rápido e simples:

https://github.com/calvintwr/blitz-hermite-resize

const blitz = Blitz.create()

/* Promise */
blitz({
    source: DOM Image/DOM Canvas/jQuery/DataURL/File,
    width: 400,
    height: 600
}).then(output => {
    // handle output
})catch(error => {
    // handle error
})

/* Await */
let resized = await blizt({...})

/* Old school callback */
const blitz = Blitz.create('callback')
blitz({...}, function(output) {
    // run your callback.
})

História

Isso é realmente depois de muitas rodadas de pesquisa, leitura e tentativa.

O algoritmo de redimensionamento usa o script Hermite do @ ViliusL (o redimensionador Hermite é realmente o mais rápido e produz resultados razoavelmente bons). Ampliado com os recursos que você precisa.

Organiza um trabalhador para redimensionar para que não congele seu navegador ao redimensioná-lo, diferente de todos os outros redimensionadores de JS existentes.

Calvintwr
fonte
0

Obrigado @syockit por uma resposta incrível. no entanto, tive que reformatar um pouco da seguinte maneira para fazê-lo funcionar. Talvez devido a problemas de verificação do DOM:

$(document).ready(function () {

$('img').on("load", clickA);
function clickA() {
    var img = this;
    var canvas = document.createElement("canvas");
    new thumbnailer(canvas, img, 50, 3);
    document.body.appendChild(canvas);
}

function thumbnailer(elem, img, sx, lobes) {
    this.canvas = elem;
    elem.width = img.width;
    elem.height = img.height;
    elem.style.display = "none";
    this.ctx = elem.getContext("2d");
    this.ctx.drawImage(img, 0, 0);
    this.img = img;
    this.src = this.ctx.getImageData(0, 0, img.width, img.height);
    this.dest = {
        width: sx,
        height: Math.round(img.height * sx / img.width)
    };
    this.dest.data = new Array(this.dest.width * this.dest.height * 3);
    this.lanczos = lanczosCreate(lobes);
    this.ratio = img.width / sx;
    this.rcp_ratio = 2 / this.ratio;
    this.range2 = Math.ceil(this.ratio * lobes / 2);
    this.cacheLanc = {};
    this.center = {};
    this.icenter = {};
    setTimeout(process1, 0, this, 0);
}

//returns a function that calculates lanczos weight
function lanczosCreate(lobes) {
    return function (x) {
        if (x > lobes)
            return 0;
        x *= Math.PI;
        if (Math.abs(x) < 1e-16)
            return 1
        var xx = x / lobes;
        return Math.sin(x) * Math.sin(xx) / x / xx;
    }
}

process1 = function (self, u) {
    self.center.x = (u + 0.5) * self.ratio;
    self.icenter.x = Math.floor(self.center.x);
    for (var v = 0; v < self.dest.height; v++) {
        self.center.y = (v + 0.5) * self.ratio;
        self.icenter.y = Math.floor(self.center.y);
        var a, r, g, b;
        a = r = g = b = 0;
        for (var i = self.icenter.x - self.range2; i <= self.icenter.x + self.range2; i++) {
            if (i < 0 || i >= self.src.width)
                continue;
            var f_x = Math.floor(1000 * Math.abs(i - self.center.x));
            if (!self.cacheLanc[f_x])
                self.cacheLanc[f_x] = {};
            for (var j = self.icenter.y - self.range2; j <= self.icenter.y + self.range2; j++) {
                if (j < 0 || j >= self.src.height)
                    continue;
                var f_y = Math.floor(1000 * Math.abs(j - self.center.y));
                if (self.cacheLanc[f_x][f_y] == undefined)
                    self.cacheLanc[f_x][f_y] = self.lanczos(Math.sqrt(Math.pow(f_x * self.rcp_ratio, 2) + Math.pow(f_y * self.rcp_ratio, 2)) / 1000);
                weight = self.cacheLanc[f_x][f_y];
                if (weight > 0) {
                    var idx = (j * self.src.width + i) * 4;
                    a += weight;
                    r += weight * self.src.data[idx];
                    g += weight * self.src.data[idx + 1];
                    b += weight * self.src.data[idx + 2];
                }
            }
        }
        var idx = (v * self.dest.width + u) * 3;
        self.dest.data[idx] = r / a;
        self.dest.data[idx + 1] = g / a;
        self.dest.data[idx + 2] = b / a;
    }

    if (++u < self.dest.width)
        setTimeout(process1, 0, self, u);
    else
        setTimeout(process2, 0, self);
};

process2 = function (self) {
    self.canvas.width = self.dest.width;
    self.canvas.height = self.dest.height;
    self.ctx.drawImage(self.img, 0, 0);
    self.src = self.ctx.getImageData(0, 0, self.dest.width, self.dest.height);
    var idx, idx2;
    for (var i = 0; i < self.dest.width; i++) {
        for (var j = 0; j < self.dest.height; j++) {
            idx = (j * self.dest.width + i) * 3;
            idx2 = (j * self.dest.width + i) * 4;
            self.src.data[idx2] = self.dest.data[idx];
            self.src.data[idx2 + 1] = self.dest.data[idx + 1];
            self.src.data[idx2 + 2] = self.dest.data[idx + 2];
        }
    }
    self.ctx.putImageData(self.src, 0, 0);
    self.canvas.style.display = "block";
}
});
Manish
fonte
-1

Acabei de executar uma página de comparações lado a lado e, a menos que algo tenha mudado recentemente, não havia melhor downsizing (dimensionamento) usando canvas vs. css simples. Eu testei no FF6 Mac OSX 10.7. Ainda um pouco mole vs. o original.

No entanto, deparei-me com algo que fez uma enorme diferença e que estava usando filtros de imagem em navegadores que suportam telas. Você pode realmente manipular imagens da mesma forma que no Photoshop com desfoque, nitidez, saturação, ondulação, escala de cinza, etc.

Eu então encontrei um plug-in jQuery incrível, que facilita muito a aplicação desses filtros: http://codecanyon.net/item/jsmanipulate-jquery-image-manipulation-plugin/428234

Simplesmente aplico o filtro de nitidez logo após redimensionar a imagem, o que deve proporcionar o efeito desejado. Eu nem precisei usar um elemento de tela.

Julian Dormon
fonte
-1

Procurando outra ótima solução simples?

var img=document.createElement('img');
img.src=canvas.toDataURL();
$(img).css("background", backgroundColor);
$(img).width(settings.width);
$(img).height(settings.height);

Esta solução utilizará o algoritmo de redimensionamento do navegador! :)

ale500
fonte
A questão é reduzir a amostragem da imagem, não apenas redimensioná-la.
Jesús Carrera
[...] estou tentando redimensionar um jpg. Eu tentei redimensionar o mesmo jpg no site vinculado e no photoshop, e ele fica bem quando reduzido [...]. Por que você não pode usar o <img> Jesus Carrera?
precisa saber é o seguinte