Saltar al contenido

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

Luego de mucho trabajar hemos dado con la contestación de esta contratiempo que muchos lectores de nuestro sitio han presentado. Si deseas aportar algún detalle no dejes de compartir tu información.

Solución:

Entonces, ¿qué haces si todos los navegadores (en realidad, Chrome 5 me dio uno bastante bueno) no te dan una calidad de remuestreo lo suficientemente buena? ¡Entonces los implementa usted mismo! Vamos, estamos entrando en la nueva era de Web 3.0, navegadores compatibles con HTML5, compiladores de JavaScript JIT súper optimizados, máquinas de varios núcleos (†), con toneladas de memoria, ¿a qué le tienes miedo? Oye, está la palabra java en javascript, por lo que debería garantizar el rendimiento, ¿verdad? He aquí, el código de generación 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++)  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++)  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

así que 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 poner a prueba a sus mejores navegadores y ver cuál es menos probable que aumente la presión arterial de su cliente!

Umm, ¿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 el multinúcleo.

Rápido cambio de tamaño de imagen / algoritmo de remuestreo usando Hermite filtrar 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 hice funcionar!

Git: https://github.com/viliusle/Hermite-resize
Demostración: 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);

Prueba pica: es un redimensionador altamente optimizado con algoritmos seleccionables. Ver demostración.

Por ejemplo, la imagen original de la primera publicación se redimensiona en 120ms con filtro Lanczos y ventana de 3px o 60ms con filtro Box y ventana de 0.5px. Para una imagen enorme de 17 MB, 5000x3000px, el cambio de tamaño toma ~ 1 s en el escritorio y 3 s 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á muy bien optimizado para los JIT-s modernos y está listo para usar de inmediato (a través de npm o bower). Además, utiliza webworkers cuando está disponible para evitar que la interfaz se congele.

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

Calificaciones y comentarios

Puedes añadir valor a nuestra información participando con tu experiencia en las interpretaciones.

¡Haz clic para puntuar esta entrada!
(Votos: 2 Promedio: 4)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *