javascript - una - uso del atributo alt en html




Imagen de tamaño de lienzo HTML5(Downscale) de alta calidad? (7)

Sugerencia 1: extender el proceso de la línea de tubería

Puede utilizar la reducción como lo describo en los enlaces a los que hace referencia, pero parece que los usa de forma incorrecta.

Bajar no es necesario para escalar imágenes a relaciones superiores a 1: 2 (típicamente, pero no limitado a). Es donde necesita hacer una reducción drástica de escala para dividirla en dos (y raramente, más) pasos dependiendo del contenido de la imagen (en particular cuando se producen altas frecuencias, como líneas finas).

Cada vez que muestres una imagen, perderás detalles e información. No puede esperar que la imagen resultante sea tan clara como la original.

Si está reduciendo las imágenes en muchos pasos, perderá mucha información en total y el resultado será pobre como ya ha notado.

Pruebe con solo un paso extra, o en la parte superior dos.

Convoluciones

En el caso de Photoshop, tenga en cuenta que aplica una convolución después de que la imagen se haya vuelto a muestrear, como afilar. No se trata únicamente de la interpolación bicúbica, por lo que, para emular completamente Photoshop, también debemos agregar los pasos que está haciendo Photoshop (con la configuración predeterminada).

Para este ejemplo usaré mi respuesta original a la que se refiere en su publicación, pero le agregué una convolución de afilado para mejorar la calidad como un proceso posterior (vea la demostración en la parte inferior).

Aquí hay un código para agregar el filtro de nitidez (se basa en un filtro de convolución genérica; puse la matriz de peso para afilar dentro de ella así como un factor de mezcla para ajustar la pronunciación del efecto):

Uso:

sharpen(context, width, height, mixFactor);

mixFactor tiene un valor entre [0.0, 1.0] y le permite minimizar el efecto de nitidez: regla de oro: cuanto menor sea el tamaño, menos efecto se necesita.

Función (basada en este fragmento ):

function sharpen(ctx, w, h, mix) {

    var weights =  [0, -1, 0,  -1, 5, -1,  0, -1, 0],
        katet = Math.round(Math.sqrt(weights.length)),
        half = (katet * 0.5) |0,
        dstData = ctx.createImageData(w, h),
        dstBuff = dstData.data,
        srcBuff = ctx.getImageData(0, 0, w, h).data,
        y = h;

    while(y--) {

        x = w;

        while(x--) {

            var sy = y,
                sx = x,
                dstOff = (y * w + x) * 4,
                r = 0, g = 0, b = 0, a = 0;

            for (var cy = 0; cy < katet; cy++) {
                for (var cx = 0; cx < katet; cx++) {

                    var scy = sy + cy - half;
                    var scx = sx + cx - half;

                    if (scy >= 0 && scy < h && scx >= 0 && scx < w) {

                        var srcOff = (scy * w + scx) * 4;
                        var wt = weights[cy * katet + cx];

                        r += srcBuff[srcOff] * wt;
                        g += srcBuff[srcOff + 1] * wt;
                        b += srcBuff[srcOff + 2] * wt;
                        a += srcBuff[srcOff + 3] * wt;
                    }
                }
            }

            dstBuff[dstOff] = r * mix + srcBuff[dstOff] * (1 - mix);
            dstBuff[dstOff + 1] = g * mix + srcBuff[dstOff + 1] * (1 - mix);
            dstBuff[dstOff + 2] = b * mix + srcBuff[dstOff + 2] * (1 - mix)
            dstBuff[dstOff + 3] = srcBuff[dstOff + 3];
        }
    }

    ctx.putImageData(dstData, 0, 0);
}

El resultado de usar esta combinación será:

DEMO EN LÍNEA AQUÍ

Según la cantidad de nitidez que desee agregar a la mezcla, puede obtener el resultado de "borroso" por defecto a muy nítido:

Sugerencia 2: implementación de algoritmo de bajo nivel

Si desea obtener el mejor resultado en cuanto a la calidad, tendrá que ir a bajo nivel y considerar implementar, por ejemplo, este nuevo algoritmo para hacer esto.

Vea Reducción de imagen de dependiente de interpolación (2011) de IEEE.
Aquí hay un enlace al documento completo (PDF) .

No hay implementaciones de este algoritmo en JavaScript AFAIK de en este momento, por lo que te conviene que te lleves a mano si quieres lanzarte a esta tarea.

La esencia es (extractos del artículo):

Abstracto

En este trabajo, se propone un algoritmo de muestreo descendente adaptativo orientado a la interpolación para la codificación de imágenes de baja velocidad binaria. Dada una imagen, el algoritmo propuesto es capaz de obtener una imagen de baja resolución, a partir de la cual se puede interpolar una imagen de alta calidad con la misma resolución que la imagen de entrada. A diferencia de los algoritmos de muestreo descendentes tradicionales, que son independientes del proceso de interpolación, el algoritmo de muestreo descendente propuesto vincula el muestreo descendente al proceso de interpolación. En consecuencia, el algoritmo de muestreo descendente propuesto puede mantener la información original de la imagen de entrada en la mayor medida posible. La imagen muestreada hacia abajo se alimenta a JPEG. A continuación, se aplica un postprocesamiento de variación total (TV) a la imagen de baja resolución descomprimida. Finalmente, la imagen procesada se interpola para mantener la resolución original de la imagen de entrada. Los resultados experimentales verifican que al utilizar la imagen muestreada por el algoritmo propuesto, se puede lograr una imagen interpolada con una calidad mucho más alta. Además, el algoritmo propuesto es capaz de lograr un rendimiento superior a JPEG para la codificación de imágenes de baja tasa de bits.

(ver el enlace proporcionado para todos los detalles, fórmulas, etc.)

Uso elementos de lienzo html5 para cambiar el tamaño de las imágenes en mi navegador. Resulta que la calidad es muy baja. Encontré esto: deshabilite la interpolación al escalar un <canvas> pero no ayuda a aumentar la calidad.

A continuación se muestra mi código css y js, así como la imagen escalada con Photoshop y escalada en la API de lienzo.

¿Qué debo hacer para obtener una calidad óptima al escalar una imagen en el navegador?

Nota: Quiero escalar una imagen grande a una pequeña, modificar el color en un lienzo y enviar el resultado del lienzo al servidor.

CSS:

canvas, img {
    image-rendering: optimizeQuality;
    image-rendering: -moz-crisp-edges;
    image-rendering: -webkit-optimize-contrast;
    image-rendering: optimize-contrast;
    -ms-interpolation-mode: nearest-neighbor;
}

JS:

var $img = $('<img>');
var $originalCanvas = $('<canvas>');
$img.load(function() {


   var originalContext = $originalCanvas[0].getContext('2d');   
   originalContext.imageSmoothingEnabled = false;
   originalContext.webkitImageSmoothingEnabled = false;
   originalContext.mozImageSmoothingEnabled = false;
   originalContext.drawImage(this, 0, 0, 379, 500);
});

La imagen cambia de tamaño con Photoshop:

La imagen cambia de tamaño en el lienzo:

Editar:

Traté de hacer downscaling en más de un paso como se propone en:

Cambiar el tamaño de una imagen en un lienzo HTML5 y lienzo Html5 drawImage: cómo aplicar antialiasing

Esta es la función que he usado:

function resizeCanvasImage(img, canvas, maxWidth, maxHeight) {
    var imgWidth = img.width, 
        imgHeight = img.height;

    var ratio = 1, ratio1 = 1, ratio2 = 1;
    ratio1 = maxWidth / imgWidth;
    ratio2 = maxHeight / imgHeight;

    // Use the smallest ratio that the image best fit into the maxWidth x maxHeight box.
    if (ratio1 < ratio2) {
        ratio = ratio1;
    }
    else {
        ratio = ratio2;
    }

    var canvasContext = canvas.getContext("2d");
    var canvasCopy = document.createElement("canvas");
    var copyContext = canvasCopy.getContext("2d");
    var canvasCopy2 = document.createElement("canvas");
    var copyContext2 = canvasCopy2.getContext("2d");
    canvasCopy.width = imgWidth;
    canvasCopy.height = imgHeight;  
    copyContext.drawImage(img, 0, 0);

    // init
    canvasCopy2.width = imgWidth;
    canvasCopy2.height = imgHeight;        
    copyContext2.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvasCopy2.width, canvasCopy2.height);


    var rounds = 2;
    var roundRatio = ratio * rounds;
    for (var i = 1; i <= rounds; i++) {
        console.log("Step: "+i);

        // tmp
        canvasCopy.width = imgWidth * roundRatio / i;
        canvasCopy.height = imgHeight * roundRatio / i;

        copyContext.drawImage(canvasCopy2, 0, 0, canvasCopy2.width, canvasCopy2.height, 0, 0, canvasCopy.width, canvasCopy.height);

        // copy back
        canvasCopy2.width = imgWidth * roundRatio / i;
        canvasCopy2.height = imgHeight * roundRatio / i;
        copyContext2.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvasCopy2.width, canvasCopy2.height);

    } // end for


    // copy back to canvas
    canvas.width = imgWidth * roundRatio / rounds;
    canvas.height = imgHeight * roundRatio / rounds;
    canvasContext.drawImage(canvasCopy2, 0, 0, canvasCopy2.width, canvasCopy2.height, 0, 0, canvas.width, canvas.height);


}

Este es el resultado si uso un tamaño de 2 pasos:

Este es el resultado si uso un tamaño de 3 pasos:

Este es el resultado si uso un tamaño de 4 pasos:

Este es el resultado si uso un tamaño de 20 pasos:

Nota: Resulta que de 1 paso a 2 pasos hay una gran mejora en la calidad de la imagen, pero cuantos más pasos agregue al proceso, más borrosa será la imagen.

¿Hay alguna manera de resolver el problema de que la imagen se vuelva más difusa cuanto más pasos agregue?

Editar 2013-10-04: Probé el algoritmo de GameAlchemist. Aquí está el resultado comparado con Photoshop.

Imagen de PhotoShop:

Algoritmo de GameAlchemist:


¿Por qué usar el lienzo para cambiar el tamaño de las imágenes? Los navegadores modernos usan interpolación bicúbica, el mismo proceso utilizado por Photoshop (si lo estás haciendo bien), y lo hacen más rápido que el proceso de lienzo. Simplemente especifique el tamaño de imagen que desea (use solo una dimensión, alto o ancho, para cambiar el tamaño proporcionalmente).

Esto es compatible con la mayoría de los navegadores, incluidas las versiones posteriores de IE. Las versiones anteriores pueden requerir CSS específico del navegador .

Una función simple (usando jQuery) para cambiar el tamaño de una imagen sería así:

function resizeImage(img, percentage) {
    var coeff = percentage/100,
        width = $(img).width(),
        height = $(img).height();

    return {"width": width*coeff, "height": height*coeff}           
}

EDITAR Cambió la image a img para que coincida con los argumentos de la función. ^) ^

Luego, simplemente use el valor devuelto para cambiar el tamaño de la imagen en una o ambas dimensiones.

Obviamente, hay diferentes mejoras que podría hacer, pero esto hace el trabajo.

APÉNDICE

Pegue el siguiente código en la consola de esta página y observe lo que sucede con los gravatares:

function resizeImage(img, percentage) {
    var coeff = percentage/100,
        width = $(img).width(),
        height = $(img).height();

    return {"width": width*coeff, "height": height*coeff}           
}

$('.user-gravatar32 img').each(function(){
  var newDimensions = resizeImage( this, 150);
  this.style.width = newDimensions.width + "px";
  this.style.height = newDimensions.height + "px";
});

Dado que su problema es reducir la escala de su imagen, no tiene sentido hablar de interpolación, que se trata de crear píxeles. El problema aquí es la reducción de muestreo.

Para reducir la resolución de una imagen, debemos convertir cada cuadrado de p * p píxeles en la imagen original en un único píxel en la imagen de destino.

Por motivos de rendimiento, los navegadores realizan un muestreo muy simple: para construir la imagen más pequeña, solo elegirán UN píxel en la fuente y usarán su valor para el destino. que 'olvida' algunos detalles y agrega ruido.

Sin embargo, hay una excepción: dado que la resolución doble de la imagen 2X es muy simple de computar (promedio de 4 píxeles para hacer una) y se usa para píxeles retina / HiDPI, este caso se maneja correctamente: el navegador usa 4 píxeles para hacer uno-.

PERO ... si usa varias veces una reducción de muestreo 2X, se enfrentará al problema de que los errores de redondeo sucesivos agregarán demasiado ruido.
Lo que es peor, no siempre cambiarás el tamaño con una potencia de dos, y cambiarás el tamaño a la potencia más cercana + un último cambio de tamaño es muy ruidoso.

Lo que buscas es una reducción de resolución de píxeles, es decir: un nuevo muestreo de la imagen que tendrá en cuenta todos los píxeles de entrada, independientemente de la escala.
Para ello, debemos calcular, para cada píxel de entrada, su contribución a uno, dos o cuatro píxeles de destino, dependiendo de si la proyección escalada de los píxeles de entrada está dentro de los píxeles de destino, solapa un borde X, un borde Y o ambos .
(Un esquema sería bueno aquí, pero no tengo uno).

Aquí hay un ejemplo de escala de lienzo frente a mi escala perfecta de píxeles en una escala 1/3 de un zombi.

Tenga en cuenta que la imagen puede escalarse en su navegador, y es .jpegized por SO.
Sin embargo, vemos que hay mucho menos ruido, especialmente en la hierba detrás del wombat y en las ramas a su derecha. El ruido en el pelaje lo hace más contrastado, pero parece que tiene pelos blancos, a diferencia de la imagen original.
La imagen de la derecha es menos pegadiza pero definitivamente más agradable.

Aquí está el código para hacer la reducción de escala de píxeles perfecta:

Resultado del violín: http://jsfiddle.net/gamealchemist/r6aVp/embedded/result/
violín en sí mismo: http://jsfiddle.net/gamealchemist/r6aVp/

// scales the image by (float) scale < 1
// returns a canvas containing the scaled image.
function downScaleImage(img, scale) {
    var imgCV = document.createElement('canvas');
    imgCV.width = img.width;
    imgCV.height = img.height;
    var imgCtx = imgCV.getContext('2d');
    imgCtx.drawImage(img, 0, 0);
    return downScaleCanvas(imgCV, scale);
}

// scales the canvas by (float) scale < 1
// returns a new canvas containing the scaled image.
function downScaleCanvas(cv, scale) {
    if (!(scale < 1) || !(scale > 0)) throw ('scale must be a positive number <1 ');
    var sqScale = scale * scale; // square scale = area of source pixel within target
    var sw = cv.width; // source image width
    var sh = cv.height; // source image height
    var tw = Math.floor(sw * scale); // target image width
    var th = Math.floor(sh * scale); // target image height
    var sx = 0, sy = 0, sIndex = 0; // source x,y, index within source array
    var tx = 0, ty = 0, yIndex = 0, tIndex = 0; // target x,y, x,y index within target array
    var tX = 0, tY = 0; // rounded tx, ty
    var w = 0, nw = 0, wx = 0, nwx = 0, wy = 0, nwy = 0; // weight / next weight x / y
    // weight is weight of current source point within target.
    // next weight is weight of current source point within next target's point.
    var crossX = false; // does scaled px cross its current px right border ?
    var crossY = false; // does scaled px cross its current px bottom border ?
    var sBuffer = cv.getContext('2d').
    getImageData(0, 0, sw, sh).data; // source buffer 8 bit rgba
    var tBuffer = new Float32Array(3 * tw * th); // target buffer Float32 rgb
    var sR = 0, sG = 0,  sB = 0; // source's current point r,g,b
    /* untested !
    var sA = 0;  //source alpha  */    

    for (sy = 0; sy < sh; sy++) {
        ty = sy * scale; // y src position within target
        tY = 0 | ty;     // rounded : target pixel's y
        yIndex = 3 * tY * tw;  // line index within target array
        crossY = (tY != (0 | ty + scale)); 
        if (crossY) { // if pixel is crossing botton target pixel
            wy = (tY + 1 - ty); // weight of point within target pixel
            nwy = (ty + scale - tY - 1); // ... within y+1 target pixel
        }
        for (sx = 0; sx < sw; sx++, sIndex += 4) {
            tx = sx * scale; // x src position within target
            tX = 0 |  tx;    // rounded : target pixel's x
            tIndex = yIndex + tX * 3; // target pixel index within target array
            crossX = (tX != (0 | tx + scale));
            if (crossX) { // if pixel is crossing target pixel's right
                wx = (tX + 1 - tx); // weight of point within target pixel
                nwx = (tx + scale - tX - 1); // ... within x+1 target pixel
            }
            sR = sBuffer[sIndex    ];   // retrieving r,g,b for curr src px.
            sG = sBuffer[sIndex + 1];
            sB = sBuffer[sIndex + 2];

            /* !! untested : handling alpha !!
               sA = sBuffer[sIndex + 3];
               if (!sA) continue;
               if (sA != 0xFF) {
                   sR = (sR * sA) >> 8;  // or use /256 instead ??
                   sG = (sG * sA) >> 8;
                   sB = (sB * sA) >> 8;
               }
            */
            if (!crossX && !crossY) { // pixel does not cross
                // just add components weighted by squared scale.
                tBuffer[tIndex    ] += sR * sqScale;
                tBuffer[tIndex + 1] += sG * sqScale;
                tBuffer[tIndex + 2] += sB * sqScale;
            } else if (crossX && !crossY) { // cross on X only
                w = wx * scale;
                // add weighted component for current px
                tBuffer[tIndex    ] += sR * w;
                tBuffer[tIndex + 1] += sG * w;
                tBuffer[tIndex + 2] += sB * w;
                // add weighted component for next (tX+1) px                
                nw = nwx * scale
                tBuffer[tIndex + 3] += sR * nw;
                tBuffer[tIndex + 4] += sG * nw;
                tBuffer[tIndex + 5] += sB * nw;
            } else if (crossY && !crossX) { // cross on Y only
                w = wy * scale;
                // add weighted component for current px
                tBuffer[tIndex    ] += sR * w;
                tBuffer[tIndex + 1] += sG * w;
                tBuffer[tIndex + 2] += sB * w;
                // add weighted component for next (tY+1) px                
                nw = nwy * scale
                tBuffer[tIndex + 3 * tw    ] += sR * nw;
                tBuffer[tIndex + 3 * tw + 1] += sG * nw;
                tBuffer[tIndex + 3 * tw + 2] += sB * nw;
            } else { // crosses both x and y : four target points involved
                // add weighted component for current px
                w = wx * wy;
                tBuffer[tIndex    ] += sR * w;
                tBuffer[tIndex + 1] += sG * w;
                tBuffer[tIndex + 2] += sB * w;
                // for tX + 1; tY px
                nw = nwx * wy;
                tBuffer[tIndex + 3] += sR * nw;
                tBuffer[tIndex + 4] += sG * nw;
                tBuffer[tIndex + 5] += sB * nw;
                // for tX ; tY + 1 px
                nw = wx * nwy;
                tBuffer[tIndex + 3 * tw    ] += sR * nw;
                tBuffer[tIndex + 3 * tw + 1] += sG * nw;
                tBuffer[tIndex + 3 * tw + 2] += sB * nw;
                // for tX + 1 ; tY +1 px
                nw = nwx * nwy;
                tBuffer[tIndex + 3 * tw + 3] += sR * nw;
                tBuffer[tIndex + 3 * tw + 4] += sG * nw;
                tBuffer[tIndex + 3 * tw + 5] += sB * nw;
            }
        } // end for sx 
    } // end for sy

    // create result canvas
    var resCV = document.createElement('canvas');
    resCV.width = tw;
    resCV.height = th;
    var resCtx = resCV.getContext('2d');
    var imgRes = resCtx.getImageData(0, 0, tw, th);
    var tByteBuffer = imgRes.data;
    // convert float32 array into a UInt8Clamped Array
    var pxIndex = 0; //  
    for (sIndex = 0, tIndex = 0; pxIndex < tw * th; sIndex += 3, tIndex += 4, pxIndex++) {
        tByteBuffer[tIndex] = Math.ceil(tBuffer[sIndex]);
        tByteBuffer[tIndex + 1] = Math.ceil(tBuffer[sIndex + 1]);
        tByteBuffer[tIndex + 2] = Math.ceil(tBuffer[sIndex + 2]);
        tByteBuffer[tIndex + 3] = 255;
    }
    // writing result to canvas.
    resCtx.putImageData(imgRes, 0, 0);
    return resCV;
}

Es bastante ambicioso para la memoria, ya que se requiere un búfer flotante para almacenar los valores intermedios de la imagen de destino (-> si contamos el lienzo de resultados, usamos 6 veces la memoria de la imagen de origen en este algoritmo).
También es bastante costoso, ya que cada píxel fuente se utiliza independientemente del tamaño del destino, y tenemos que pagar por el getImageData / putImageDate, bastante lento también.
Pero no hay forma de ser más rápido que procesar cada valor de fuente en este caso, y la situación no es tan mala: para mi imagen 740 * 556 de un wombat, el procesamiento lleva entre 30 y 40 ms.


Encontré una solución que no necesita acceder directamente a los datos de píxeles y recorrerlos para realizar el muestreo descendente. 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 () usa un método de remuestreo de vecino de interpolación lineal. Eso funciona bien cuando no está reduciendo el tamaño más de la mitad del tamaño original .

Si realiza un ciclo para cambiar el tamaño máximo de medio medio por vez, los resultados serían bastante buenos y mucho más rápidos que el acceso a datos de píxeles.

Esta función se reduce 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 para esta publicación


No es la respuesta correcta para las personas que realmente necesitan cambiar el tamaño de la imagen en sí, sino simplemente para reducir el tamaño del archivo .

Tuve un problema con las imágenes "directamente desde la cámara", que mis clientes a menudo subían en JPEG "sin comprimir".

No tan conocido es, que el lienzo es compatible (en la mayoría de los navegadores de 2017) para cambiar la calidad de JPEG

data=canvas.toDataURL('image/jpeg', .85) # [1..0] default 0.92

Con este truco podría reducir 4k x 3k fotos con> 10Mb a 1 o 2Mb, seguro que depende de sus necesidades.

mira aquí


Rápido remuestreo de lienzo con buena calidad: http://jsfiddle.net/9g9Nv/442/

Actualización: versión 2.0 (más rápido, trabajadores web + objetos transferibles) - https://github.com/viliusle/Hermite-resize

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

context.scale(xScale, yScale)

<canvas id="c"></canvas>
<hr/>
<img id="i" />

<script>
var i = document.getElementById('i');

i.onload = function(){
    var width = this.naturalWidth,
        height = this.naturalHeight,
        canvas = document.getElementById('c'),
        ctx = canvas.getContext('2d');

    canvas.width = Math.floor(width / 2);
    canvas.height = Math.floor(height / 2);

    ctx.scale(0.5, 0.5);
    ctx.drawImage(this, 0, 0);
    ctx.rect(0,0,500,500);
    ctx.stroke();

    // restore original 1x1 scale
    ctx.scale(2, 2);
    ctx.rect(0,0,500,500);
    ctx.stroke();
};

i.src = 'https://static.md/b70a511140758c63f07b618da5137b5d.png';
</script>






html5-canvas