Cambiar el tamaño de una imagen en un lienzo HTML5


314

Estoy tratando de crear una imagen en miniatura en el lado del cliente usando JavaScript y un elemento de lienzo, pero cuando reduzco la imagen, se ve terrible. Parece que se redujo en Photoshop con el conjunto de remuestreo en 'Vecino más cercano' en lugar de Bicubic. Sé que es posible hacer que esto se vea bien, porque este sitio también puede hacerlo bien utilizando un lienzo. Intenté usar el mismo código que hacen como se muestra en el enlace "[Fuente]", pero aún se ve terrible. ¿Hay algo que me falta, alguna configuración que deba configurarse o algo así?

EDITAR:

Estoy tratando de cambiar el tamaño de un jpg. He intentado cambiar el tamaño del mismo jpg en el sitio vinculado y en Photoshop, y se ve bien cuando se reduce el tamaño.

Aquí está el 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 me equivoqué, el sitio web vinculado no estaba haciendo un mejor trabajo al reducir el tamaño de la imagen. Probé los otros métodos sugeridos y ninguno de ellos se ve mejor. Esto es lo que resultó en los diferentes métodos:

Photoshop:

texto alternativo

Lona:

texto alternativo

Imagen con renderizado de imagen: optimizar conjunto de calidad y escala con ancho / alto:

texto alternativo

Imagen con representación de imagen: conjunto de optimización de calidad y escala con -moz-transform:

texto alternativo

Cambiar el tamaño del lienzo en pixastic:

texto alternativo

Supongo que esto significa que Firefox no está utilizando el muestreo bicúbico como se supone que debe hacerlo. Solo tendré que esperar hasta que realmente lo agreguen.

EDITAR3:

Imagen original


¿Puedes publicar el código que estás usando para cambiar el tamaño de la imagen?
Xavi

¿Estás intentando cambiar el tamaño de una imagen GIF o una imagen similar con una paleta limitada? Incluso en Photoshop, estas imágenes no se reducen bien a menos que las conviertas a RGB.
leepowers

¿Puedes publicar una copia de la imagen original?
Xavi

Cambiar el tamaño de la imagen usando JavaScript es un poco complicado: no solo está utilizando la potencia de procesamiento del cliente para cambiar el tamaño de la imagen, sino que lo está haciendo en cada carga de página. ¿Por qué no simplemente guardar una versión reducida de Photoshop y servirla en su lugar / junto con la imagen original?
define el

29
Porque estoy haciendo un cargador de imágenes con la capacidad de cambiar el tamaño y recortar las imágenes antes de cargarlas.
Telanor

Respuestas:


393

Entonces, ¿qué haces si todos los navegadores (en realidad, Chrome 5 me dio uno bastante bueno) no te darán una calidad de muestreo lo suficientemente buena? ¡Los implementas tú mismo entonces! Oh, vamos, estamos entrando en la nueva era de la Web 3.0, navegadores compatibles con HTML5, compiladores jait JIT súper optimizados, máquinas multinúcleo (†), con toneladas de memoria, ¿de qué tienes miedo? Hola, está la palabra java en javascript, así que eso debería garantizar el rendimiento, ¿verdad? He aquí, el código generador 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";
};

... con el que puedes producir resultados como estos!

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

de todos modos, aquí hay una versión 'fija' de su ejemplo:

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);
};

¡Ahora es el momento de enfrentar sus mejores navegadores y ver cuál es menos probable que aumente la presión arterial de su cliente!

¿Dónde está mi etiqueta de sarcasmo?

(dado que muchas partes del código se basan en Anrieff Gallery Generator , ¿también está cubierto por GPL2? No lo sé)

† en realidad debido a la limitación de javascript, no se admite multi-core.


2
En realidad, intenté implementarlo yo mismo, haciendo lo que hiciste, copiando el código de un editor de imágenes de código abierto. Como no pude encontrar ninguna documentación sólida sobre el algoritmo, tuve dificultades para optimizarlo. Al final, la mía fue un poco lenta (tardó unos segundos en cambiar el tamaño de la imagen). Cuando tenga la oportunidad, probaré el tuyo y veré si es más rápido. Y creo que los trabajadores web hacen posible javascript multi-core ahora. Intentaría usarlos para acelerarlo, pero estaba teniendo problemas para descubrir cómo convertir esto en un algoritmo multiproceso
Telanor

3
Lo siento, lo olvidé! He editado la respuesta. De todos modos, no será rápido, bicubic debería ser más rápido. Sin mencionar que el algoritmo que utilicé no es el cambio de tamaño habitual de 2 vías (que es línea por línea, horizontal y vertical), por lo que es mucho más lento.
syockit

55
Eres increíble y mereces toneladas de asombro.
Rocklan

55
Esto produce resultados decentes, pero tarda 7,4 segundos para una imagen de 1,8 MP en la última versión de Chrome ...
mpen

2
¿Cómo los métodos como este logran una puntuación tan alta? La solución que se muestra por completo no tiene en cuenta la escala logarítmica utilizada para almacenar información de color. Un RGB de 127,127,127 es un cuarto del brillo de 255, 255, 255, no la mitad. El muestreo descendente en la solución da como resultado una imagen oscura. Es una pena que esto esté cerrado ya que hay un método muy simple y rápido para reducir el tamaño que produce resultados aún mejores que la muestra de Photoshop (OP debe haber tenido las preferencias establecidas incorrectamente)
Blindman67

37

Algoritmo rápido de redimensionamiento / remuestreo de imágenes usando el filtro Hermite con JavaScript. Apoya la transparencia, da buena calidad. Avance:

ingrese la descripción de la imagen aquí

Actualizar : versión 2.0 agregada en GitHub (más rápido, trabajadores web + objetos transferibles). ¡Finalmente lo conseguí funcionando!

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);
}

¿Quizás pueda incluir enlaces a su demostración de miniPaint y al repositorio de Github?
syockit

1
¿También compartirás la versión para webworkers? Probablemente debido a la sobrecarga de configuración, es más lento para imágenes pequeñas, pero podría ser útil para imágenes de origen más grandes.
syockit

demostración adicional, enlaces git, también versión multi-core. Por cierto, no pasé demasiado tiempo optimizando la versión multinúcleo ... Creo que la versión única está bien optimizada.
ViliusL

Gran diferencia y rendimiento decente. ¡Muchas gracias! antes y después
KevBurnsJr

2
@ViliusL Ah, ahora recordaba por qué los trabajadores web no funcionaban tan bien. No habían compartido memoria antes, ¡y todavía no la tienen ahora! Tal vez algún día, cuando se las arreglan para solucionar el problema, el código vendrá a su uso (que, o tal vez la gente usa PNaCl)
syockit

26

Prueba pica , que es un redimensionador altamente optimizado con algoritmos seleccionables. Ver demo .

Por ejemplo, la imagen original de la primera publicación se redimensiona en 120 ms con filtro Lanczos y ventana de 3 píxeles o 60 ms con filtro de cuadro y ventana de 0,5 píxeles. Para una gran imagen de 17mb, el tamaño de 5000x3000px toma ~ 1s en el escritorio y 3s en el móvil

Todos los principios de cambio de tamaño se describieron muy bien en este hilo, y pica no agrega ciencia espacial. Pero está optimizado muy bien para los JIT-s modernos, y está listo para usar de inmediato (a través de npm o bower). Además, utiliza trabajadores web cuando están disponibles para evitar congelaciones de la interfaz.

También planeo agregar soporte de máscara de enfoque pronto, porque es muy útil después de la reducción de escala.


14

Sé que este es un hilo viejo, pero podría ser útil para algunas personas como yo que meses después están abordando este problema por primera vez.

Aquí hay un código que cambia el tamaño de la imagen cada vez que vuelve a cargar la imagen. Soy consciente de que esto no es óptimo en absoluto, pero lo proporciono como prueba de concepto.

Además, lo siento por usar jQuery para selectores simples, pero me siento muy cómodo con la sintaxis.

$(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>

Mi función createImage se llama una vez cuando se carga el documento y luego se llama cada vez que la ventana recibe un evento de cambio de tamaño.

Lo probé en Chrome 6 y Firefox 3.6, ambos en Mac. Esta "técnica" se come al procesador como si fuera helado en el verano, pero funciona.


9

He presentado algunos algoritmos para hacer la interpolación de imágenes en matrices de píxeles de lienzo html que podrían ser útiles aquí:

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

Estos se pueden copiar / pegar y se pueden usar dentro de los trabajadores web para cambiar el tamaño de las imágenes (o cualquier otra operación que requiera interpolación; las estoy usando para desafiar imágenes en este momento).

No he agregado las cosas de lanczos arriba, así que siéntete libre de agregarlas como una comparación si lo deseas.


6

Esta es una función de JavaScript adaptada del código de @ Telanor. Al pasar una imagen base64 como primer argumento a la función, devuelve la base64 de la imagen redimensionada. maxWidth y maxHeight son opcionales.

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();

}

su enfoque es muy rápido pero produce una imagen borrosa como puede ver aquí: stackoverflow.com/questions/18922880/…
confile el

6

Le recomiendo que consulte este enlace y asegúrese de que esté establecido en verdadero.

Controlar el comportamiento de escalado de la imagen

Introducido en Gecko 1.9.2 (Firefox 3.6 / Thunderbird 3.1 / Fennec 1.0)

Gecko 1.9.2 introdujo la propiedad mozImageSmoothingEnabled en el elemento del lienzo; Si este valor booleano es falso, las imágenes no se suavizarán cuando se escalen. Esta propiedad es verdadera por defecto. ver plano?

  1. cx.mozImageSmoothingEnabled = false;

5

Si simplemente está tratando de cambiar el tamaño de una imagen, le recomiendo configurar widthy heightla imagen con CSS. Aquí hay un ejemplo rápido:

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

Tenga en cuenta que heighty widthtambién se puede configurar con JavaScript. Aquí hay una muestra rápida de código:

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

Además, para asegurarse de que la imagen redimensionada se vea bien, agregue las siguientes reglas CSS al selector de imagen:

Por lo que puedo decir, todos los navegadores, excepto IE, utilizan un algoritmo bicúbico para cambiar el tamaño de las imágenes de forma predeterminada, por lo que sus imágenes redimensionadas deberían verse bien en Firefox y Chrome.

Si configura el CSS widthy heightno funciona, es posible que desee jugar con un CSS transform:

Si por alguna razón necesita usar un lienzo, tenga en cuenta que hay dos formas de cambiar el tamaño de una imagen: cambiando el tamaño del lienzo con css o dibujando la imagen en un tamaño más pequeño.

Vea esta pregunta para más detalles.


55
Ni cambiar el tamaño del lienzo ni dibujar la imagen en un tamaño más pequeño resuelve el problema (en Chrome), lamentablemente.
Néstor

1
Chrome 27 produce una buena imagen redimensionada, pero no puede copiar el resultado a un lienzo; intentar hacerlo copiará la imagen original en su lugar.
syockit

4

Para cambiar el tamaño de la imagen con un ancho inferior al original, utilizo:

    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, .... )

y funciona =).


4

Obtuve esta imagen haciendo clic derecho en el elemento de lienzo en Firefox y guardando 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 todos modos, aquí hay una versión 'fija' de su ejemplo:

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';

He intentado hacer lo que hiciste y no sale bien como el tuyo. A menos que me haya perdido algo, el único cambio que hizo fue usar la firma del método de escala en lugar del corte, ¿verdad? Por alguna razón, no funciona para mí.
Telanor

3

El problema con algunas de estas soluciones es que acceden directamente a los datos de píxeles y se repiten para realizar el muestreo. Dependiendo del tamaño de la imagen, esto puede requerir muchos recursos y sería mejor usar los algoritmos internos del navegador.

La función drawImage () está utilizando un método de remuestreo de interpolación lineal y vecino más cercano. Eso funciona bien cuando no redimensiona más de la mitad del tamaño original .

Si realiza un bucle para cambiar el tamaño solo la mitad a la vez, los resultados serían bastante buenos y mucho más rápidos que acceder a los datos de píxeles.

Esta función reduce la muestra a la mitad a la vez hasta alcanzar el tamaño deseado:

  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 a esta publicación


2

Entonces, algo interesante que encontré hace un tiempo mientras trabajaba con el lienzo podría ser útil:

Para cambiar el tamaño del control de lienzo por sí solo, debe usar los atributos height=""y width=""(o canvas.width/canvas.height elementos ). Si usa CSS para cambiar el tamaño del lienzo, en realidad estirará (es decir, redimensionará) el contenido del lienzo para que se ajuste al lienzo completo (en lugar de simplemente aumentar o disminuir el área del lienzo.

Merecería la pena intentar dibujar la imagen en un control de lienzo con los atributos de altura y anchura establecidos en el tamaño de la imagen y luego usar CSS para cambiar el tamaño del lienzo al tamaño que está buscando. Quizás esto usaría un algoritmo de cambio de tamaño diferente.

También debe tenerse en cuenta que el lienzo tiene diferentes efectos en diferentes navegadores (e incluso diferentes versiones de diferentes navegadores). Es probable que los algoritmos y las técnicas utilizados en los navegadores cambien con el tiempo (especialmente con Firefox 4 y Chrome 6 que saldrán tan pronto, lo que pondrá un gran énfasis en el rendimiento del renderizado del lienzo).

Además, es posible que desee darle una oportunidad a SVG, ya que probablemente también use un algoritmo diferente.

¡La mejor de las suertes!


1
Establecer el ancho o la altura de un lienzo a través de los atributos HTML hace que el lienzo se borre, por lo que no se puede cambiar el tamaño con ese método. Además, SVG está destinado a tratar con imágenes matemáticas. Necesito poder dibujar PNG y cosas así, para que no me ayuden.
Telanor

La configuración de la altura y el ancho del lienzo y el cambio de tamaño usando CSS no ayuda, he encontrado (en Chrome). Incluso hacer el cambio de tamaño usando -webkit-transform en lugar de CSS ancho / alto no hace que la interpolación funcione.
Nestor

2

Tengo la sensación de que el módulo que escribí producirá resultados similares al photoshop, ya que conserva los datos de color promediando, no aplicando un algoritmo. Es un poco lento, pero para mí es el mejor, ya que conserva todos los datos de color.

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

No toma al vecino más cercano y suelta otros píxeles, ni muestra un grupo y toma un promedio aleatorio. Toma la proporción exacta que cada píxel de origen debe generar en el píxel de destino. El color de píxel promedio en la fuente será el color de píxel promedio en el destino, que estas otras fórmulas, creo que no serán.

un ejemplo de cómo usarlo está en la parte inferior de https://github.com/danschumann/limby-resize

ACTUALIZACIÓN OCTUBRE 2018 : en estos días mi ejemplo es más académico que cualquier otra cosa. Webgl es más o menos 100%, por lo que sería mejor cambiar el tamaño con eso para producir resultados similares, pero más rápidos. PICA.js hace esto, creo. -


1

Convertí la respuesta de @ syockit así como el enfoque de reducción en un servicio angular reutilizable para cualquier persona interesada: https://gist.github.com/fisch0920/37bac5e741eaec60e983

Incluí ambas soluciones porque ambas tienen sus propias ventajas y desventajas. El enfoque de convolución de Lanczos es de mayor calidad a costa de ser más lento, mientras que el enfoque de reducción gradual progresiva produce resultados razonablemente antialias y es significativamente más rápido.

Ejemplo 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
    })
})

1

Resizer de imagen Javascript rápido y simple:

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.
})

Historia

Esto es realmente después de muchas rondas de investigación, lectura y prueba.

El algoritmo de cambio de tamaño utiliza el script Hermite de @ ViliusL (el cambio de tamaño de Hermite es realmente el más rápido y ofrece una salida razonablemente buena). Extendido con las características que necesita.

Bifurca a 1 trabajador para hacer el cambio de tamaño para que no congele su navegador al cambiar el tamaño, a diferencia de todos los otros tamaños de JS que existen.


0

Gracias @syockit por una respuesta increíble. Sin embargo, tuve que formatear un poco de la siguiente manera para que funcione. Quizás debido a problemas de escaneo 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";
}
});

-1

Acabo de ejecutar una página de comparaciones de lado a lado y, a menos que algo haya cambiado recientemente, no podría ver una mejor reducción (escala) usando lienzo frente a css simple. Probé en FF6 Mac OSX 10.7. Todavía un poco suave frente al original.

Sin embargo, me topé con algo que marcó una gran diferencia y que fue usar filtros de imagen en navegadores que admiten lienzos. En realidad, puede manipular imágenes como lo hace en Photoshop con desenfoque, nitidez, saturación, ondulación, escala de grises, etc.

Luego encontré un increíble complemento jQuery que hace que la aplicación de estos filtros sea fácil: http://codecanyon.net/item/jsmanipulate-jquery-image-manipulation-plugin/428234

Simplemente aplico el filtro de nitidez justo después de cambiar el tamaño de la imagen, lo que debería darle el efecto deseado. Ni siquiera tuve que usar un elemento de lienzo.


-1

¿Busca otra gran solución simple?

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

¡Esta solución utilizará el algoritmo de cambio de tamaño del navegador! :)


La pregunta es sobre reducir la imagen, no solo cambiar el tamaño.
Jesús Carrera

[...] Estoy tratando de cambiar el tamaño de un jpg. He intentado cambiar el tamaño del mismo jpg en el sitio vinculado y en Photoshop, y se ve bien cuando se reduce [...] el tamaño. ¿Por qué no puedes usar el <img> Jesus Carrera?
ale500
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.