¿Cómo escalar imágenes en un lienzo html5 con una mejor interpolación?


En primer lugar: ¿qué estoy tratando de hacer?

Tengo una aplicación para ver imágenes. Utiliza el elemento canvas para representar la imagen. Se puede acercar, se puede alejar, y se puede arrastrar alrededor. Esta parte funciona perfectamente en este momento.

Pero digamos que tengo una imagen con mucho texto. Tiene una resolución de 1200x1700, y mi lienzo tiene 1200x900. Inicialmente, cuando se aleja, esto conduce a una resolución renderizada de ~560x800.

Mi dibujo real se ve como esto:

drawImage(src, srcOffsetX, srcOffsetY, sourceViewWidth, sourceViewHeight,
destOffsetX, destOffsetY, destWidth, destHeight);

El texto pequeño en esta imagen se ve muy, muy mal, especialmente cuando se compara con otros visores de imágenes (por ejemplo, IrfanView), o incluso el elemento html .

Me di cuenta de que el algoritmo de interpolación de navegadores es la causa de este problema. La comparación de diferentes navegadores mostró que Chrome hace que las imágenes a escala de la mejor, pero todavía no es lo suficientemente bueno.

Bueno, busqué en cada rincón de las Interwebs durante 4-5 horas seguidas y no encontré lo que necesitaba. Me se encontró la opción "imageSmoothingEnabled", estilos CSS "image-rendering" que no se pueden usar en canvas, renderizado en posiciones flotantes y muchas implementaciones JavaScript de algoritmos de interpolación (esos son lejos para ralentizar para mi propósito).

Usted puede preguntar por qué estoy diciendo todo esto: para ahorrarle el tiempo para darme respuestas que ya sé :))

Entonces: ¿hay alguna buena y rápida manera de tener una mejor interpolación? Mi idea actual es crear un objeto de imagen, redimensionar esto (porque img tiene buena interpolación cuando se escala!) y lo hacen entonces. Por desgracia, la aplicación de img.el ancho parece afectar solo al ancho mostrado...

Muchas gracias de antemano por sus respuestas!! :)

Update: Gracias a Simon, pude resolver mi problema. Aquí está el algoritmo de escalado dinámico que utilicé. Observe que mantiene la relación de aspecto, el parámetro de altura es solo para evitar más computación flotante. Sólo se reduce en este momento.

scale(destWidth, destHeight){
        var start = new Date().getTime();
        var scalingSteps = 0;
        var ctx = this._sourceImageCanvasContext;
        var curWidth = this._sourceImageWidth;
        var curHeight = this._sourceImageHeight;

        var lastWidth = this._sourceImageWidth;
        var lastHeight = this._sourceImageHeight;

        var end = false;
        var scale=0.75;
        while(end==false){
            scalingSteps +=1;
            curWidth *= scale;
            curHeight *= scale;
            if(curWidth < destWidth){
                curWidth = destWidth;
                curHeight = destHeight;
                end=true;
            }
            ctx.drawImage(this._sourceImageCanvas, 0, 0, Math.round(lastWidth), Math.round(lastHeight), 0, 0, Math.round(curWidth), Math.round(curHeight));
            lastWidth = curWidth;
            lastHeight = curHeight;
        }
        var endTime =new Date().getTime();
        console.log("execution time: "+ ( endTime - start) + "ms. scale per frame: "+scale+ " scaling step count: "+scalingSteps);
    }
Author: Kai, 2013-09-12

1 answers

Necesitas "renunciar" varias veces. En lugar de escalar de una imagen muy grande a una muy pequeña, debe volver a escalarla a tamaños intermedios.

Considere una imagen que desea dibujar a escala 1/6. Usted podría hacer esto:

var w = 1280;
var h = 853;

ctx.drawImage(img, 0, 0, w/6, h/6);   

O puede dibujarlo a un lienzo en memoria a escala 1/2, luego a escala 1/2 nuevamente, luego a escala 1/2 nuevamente. El resultado es una imagen a escala 1/6, pero usamos tres pasos:

var can2 = document.createElement('canvas');
can2.width = w/2;
can2.height = w/2;
var ctx2 = can2.getContext('2d');

ctx2.drawImage(img, 0, 0, w/2, h/2);
ctx2.drawImage(can2, 0, 0, w/2, h/2, 0, 0, w/4, h/4);
ctx2.drawImage(can2, 0, 0, w/4, h/4, 0, 0, w/6, h/6);

Entonces usted puede dibujar que de nuevo a su original contexto:

ctx.drawImage(can2, 0, 0, w/6, h/6, 0, 200, w/6, h/6);

Puedes ver la diferencia en vivo, aquí:

var can = document.getElementById('canvas1');
var ctx = can.getContext('2d');

var img = new Image();
var w = 1280;
var h = 853;
img.onload = function() {
    // step it down only once to 1/6 size:
    ctx.drawImage(img, 0, 0, w/6, h/6);   
    
    // Step it down several times
    var can2 = document.createElement('canvas');
    can2.width = w/2;
    can2.height = w/2;
    var ctx2 = can2.getContext('2d');
    
    // Draw it at 1/2 size 3 times (step down three times)
    
    ctx2.drawImage(img, 0, 0, w/2, h/2);
    ctx2.drawImage(can2, 0, 0, w/2, h/2, 0, 0, w/4, h/4);
    ctx2.drawImage(can2, 0, 0, w/4, h/4, 0, 0, w/6, h/6);
    ctx.drawImage(can2, 0, 0, w/6, h/6, 0, 200, w/6, h/6);
}



img.src = 'http://upload.wikimedia.org/wikipedia/commons/thumb/a/a4/Equus_quagga_%28Namutoni%2C_2012%29.jpg/1280px-Equus_quagga_%28Namutoni%2C_2012%29.jpg'
canvas {
    border: 1px solid gray;
}
<canvas id="canvas1" width="400" height="400"></canvas>

Ver el mismo fragmento en jsfiddle.

 45
Author: Simon Sarris,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-09-23 18:49:12