Cambiar el tamaño de la imagen con el lienzo de JavaScript (sin problemas)

90

Estoy tratando de cambiar el tamaño de algunas imágenes con lienzo, pero no tengo ni idea de cómo suavizarlas. En photoshop, navegadores, etc. hay algunos algoritmos que usan (por ejemplo, bicúbicos, bilineales) pero no sé si están integrados en el lienzo o no.

Aquí está mi violín: http://jsfiddle.net/EWupT/

var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
canvas.width=300
canvas.height=234
ctx.drawImage(img, 0, 0, 300, 234);
document.body.appendChild(canvas);

La primera es una etiqueta de imagen normal redimensionada y la segunda es lienzo. Observe cómo el lienzo no es tan liso. ¿Cómo puedo lograr la 'suavidad'?

steve
fuente

Respuestas:

136

Puede utilizar la reducción de pasos para lograr mejores resultados. La mayoría de los navegadores parecen utilizar interpolación lineal en lugar de bicúbica al cambiar el tamaño de las imágenes.

( Actualización Se ha agregado una propiedad de calidad a las especificaciones, imageSmoothingQualityque actualmente solo está disponible en Chrome).

A menos que uno elija sin suavizado o vecino más cercano, el navegador siempre interpolará la imagen después de reducirla, ya que esta función es un filtro de paso bajo para evitar el alias.

Bi-linear usa 2x2 píxeles para hacer la interpolación mientras que bi-cubic usa 4x4 por lo que al hacerlo en pasos puede acercarse al resultado bi-cubic mientras usa la interpolación bi-lineal como se ve en las imágenes resultantes.

var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var img = new Image();

img.onload = function () {

    // set size proportional to image
    canvas.height = canvas.width * (img.height / img.width);

    // step 1 - resize to 50%
    var oc = document.createElement('canvas'),
        octx = oc.getContext('2d');

    oc.width = img.width * 0.5;
    oc.height = img.height * 0.5;
    octx.drawImage(img, 0, 0, oc.width, oc.height);

    // step 2
    octx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5);

    // step 3, resize to final size
    ctx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5,
    0, 0, canvas.width, canvas.height);
}
img.src = "//i.imgur.com/SHo6Fub.jpg";
<img src="//i.imgur.com/SHo6Fub.jpg" width="300" height="234">
<canvas id="canvas" width=300></canvas>

Dependiendo de cuán drástico sea su cambio de tamaño, puede omitir el paso 2 si la diferencia es menor.

En la demostración, puede ver que el nuevo resultado ahora es muy similar al elemento de imagen.


fuente
1
@steve heh, a veces suceden estas cosas :) Para las imágenes, normalmente puede anular esto configurando un css BTW.
Ken, el primer resultado funcionó muy bien, pero cuando cambio las imágenes, puedes ver que está demasiado borroso jsfiddle.net/kcHLG ¿Qué se puede hacer en este caso y en otros?
steve
@steve, puede reducir el número de pasos a solo 1 o ninguno (para algunas imágenes esto funciona bien). Vea también esta respuesta que es similar a esta, pero aquí le agregué una convolución más nítida para que pueda hacer que la imagen resultante sea más nítida después de que se haya reducido la escala.
1
@steve aquí es un violín modificado con Bill usando solo un paso adicional: jsfiddle.net/AbdiasSoftware/kcHLG/1
1
@neaumusic el código es una continuación del código de OP. Si abre el violín, verá ctx definido. Lo he incluido aquí para evitar malentendidos.
27

Dado que el violín de Trung Le Nguyen Nhat no es correcto en absoluto (solo usa la imagen original en el último paso)
escribí mi propio violín general con comparación de rendimiento:

VIOLÍN

Básicamente es:

img.onload = function() {
   var canvas = document.createElement('canvas'),
       ctx = canvas.getContext("2d"),
       oc = document.createElement('canvas'),
       octx = oc.getContext('2d');

   canvas.width = width; // destination canvas size
   canvas.height = canvas.width * img.height / img.width;

   var cur = {
     width: Math.floor(img.width * 0.5),
     height: Math.floor(img.height * 0.5)
   }

   oc.width = cur.width;
   oc.height = cur.height;

   octx.drawImage(img, 0, 0, cur.width, cur.height);

   while (cur.width * 0.5 > width) {
     cur = {
       width: Math.floor(cur.width * 0.5),
       height: Math.floor(cur.height * 0.5)
     };
     octx.drawImage(oc, 0, 0, cur.width * 2, cur.height * 2, 0, 0, cur.width, cur.height);
   }

   ctx.drawImage(oc, 0, 0, cur.width, cur.height, 0, 0, canvas.width, canvas.height);
}
Sebastián Ott
fuente
La respuesta más subestimada jamás vista.
Amsakanna
17

Creé un servicio Angular reutilizable para manejar el cambio de tamaño de imágenes / lienzos de alta calidad para cualquiera que esté interesado: https://gist.github.com/transitive-bullshit/37bac5e741eaec60e983

El servicio incluye dos soluciones porque ambas tienen sus pros / contras. 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 de escala escalonada produce resultados razonablemente antialias y es significativamente más rápido.

Uso de ejemplo:

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
fuente
Lo siento, había cambiado mi nombre de usuario de github. Recién actualizado el enlace gist.github.com/transitive-bullshit/37bac5e741eaec60e983
fisch2
3
Vi la palabra angular, tengo esa sensación divertida
SuperUberDuper
8

Si bien algunos de esos fragmentos de código son breves y funcionan, no son triviales de seguir y comprender.

Como no soy un fanático de "copiar y pegar" de stack-overflow, me gustaría que los desarrolladores entendieran el código que están insertando en su software, espero que encuentre útil lo siguiente.

DEMO : Cambiar el tamaño de las imágenes con JS y HTML Canvas Demo fiddler.

Puede encontrar 3 métodos diferentes para hacer este cambio de tamaño, que lo ayudarán a comprender cómo funciona el código y por qué.

https://jsfiddle.net/1b68eLdr/93089/

El código completo de la demostración y el método TypeScript que puede que desee utilizar en su código se puede encontrar en el proyecto GitHub.

https://github.com/eyalc4/ts-image-resizer

Este es el código final:

export class ImageTools {
base64ResizedImage: string = null;

constructor() {
}

ResizeImage(base64image: string, width: number = 1080, height: number = 1080) {
    let img = new Image();
    img.src = base64image;

    img.onload = () => {

        // Check if the image require resize at all
        if(img.height <= height && img.width <= width) {
            this.base64ResizedImage = base64image;

            // TODO: Call method to do something with the resize image
        }
        else {
            // Make sure the width and height preserve the original aspect ratio and adjust if needed
            if(img.height > img.width) {
                width = Math.floor(height * (img.width / img.height));
            }
            else {
                height = Math.floor(width * (img.height / img.width));
            }

            let resizingCanvas: HTMLCanvasElement = document.createElement('canvas');
            let resizingCanvasContext = resizingCanvas.getContext("2d");

            // Start with original image size
            resizingCanvas.width = img.width;
            resizingCanvas.height = img.height;


            // Draw the original image on the (temp) resizing canvas
            resizingCanvasContext.drawImage(img, 0, 0, resizingCanvas.width, resizingCanvas.height);

            let curImageDimensions = {
                width: Math.floor(img.width),
                height: Math.floor(img.height)
            };

            let halfImageDimensions = {
                width: null,
                height: null
            };

            // Quickly reduce the dize by 50% each time in few iterations until the size is less then
            // 2x time the target size - the motivation for it, is to reduce the aliasing that would have been
            // created with direct reduction of very big image to small image
            while (curImageDimensions.width * 0.5 > width) {
                // Reduce the resizing canvas by half and refresh the image
                halfImageDimensions.width = Math.floor(curImageDimensions.width * 0.5);
                halfImageDimensions.height = Math.floor(curImageDimensions.height * 0.5);

                resizingCanvasContext.drawImage(resizingCanvas, 0, 0, curImageDimensions.width, curImageDimensions.height,
                    0, 0, halfImageDimensions.width, halfImageDimensions.height);

                curImageDimensions.width = halfImageDimensions.width;
                curImageDimensions.height = halfImageDimensions.height;
            }

            // Now do final resize for the resizingCanvas to meet the dimension requirments
            // directly to the output canvas, that will output the final image
            let outputCanvas: HTMLCanvasElement = document.createElement('canvas');
            let outputCanvasContext = outputCanvas.getContext("2d");

            outputCanvas.width = width;
            outputCanvas.height = height;

            outputCanvasContext.drawImage(resizingCanvas, 0, 0, curImageDimensions.width, curImageDimensions.height,
                0, 0, width, height);

            // output the canvas pixels as an image. params: format, quality
            this.base64ResizedImage = outputCanvas.toDataURL('image/jpeg', 0.85);

            // TODO: Call method to do something with the resize image
        }
    };
}}
Eyal c
fuente
4

Creé una biblioteca que le permite reducir cualquier porcentaje manteniendo todos los datos de color.

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

Ese archivo lo puedes incluir en el navegador. Los resultados se verán como photoshop o magia de imágenes, conservando todos los datos de color, promediando píxeles, en lugar de tomar los cercanos y eliminar otros. No usa una fórmula para adivinar los promedios, toma el promedio exacto.

Funkodebat
fuente
1
Probablemente usaría webgl para cambiar el tamaño ahora
Funkodebat
4

Basado en la respuesta de K3N, reescribo el código generalmente para cualquiera que quiera

var oc = document.createElement('canvas'), octx = oc.getContext('2d');
    oc.width = img.width;
    oc.height = img.height;
    octx.drawImage(img, 0, 0);
    while (oc.width * 0.5 > width) {
       oc.width *= 0.5;
       oc.height *= 0.5;
       octx.drawImage(oc, 0, 0, oc.width, oc.height);
    }
    oc.width = width;
    oc.height = oc.width * img.height / img.width;
    octx.drawImage(img, 0, 0, oc.width, oc.height);

ACTUALIZAR LA DEMO DE JSFIDDLE

Aquí está mi DEMO ONLINE

Trung Le Nguyen Nhat
fuente
2
Esto no funcionará: cada vez que cambie el tamaño del lienzo, borrará su contexto. Necesitas 2 lienzos. Aquí es lo mismo que llamar directamente a drawImage con las dimensiones finales.
Kaiido
2

No entiendo por qué nadie lo sugiere createImageBitmap.

createImageBitmap(
    document.getElementById('image'), 
    { resizeWidth: 300, resizeHeight: 234, resizeQuality: 'high' }
)
.then(imageBitmap => 
    document.getElementById('canvas').getContext('2d').drawImage(imageBitmap, 0, 0)
);

funciona muy bien (suponiendo que establezca identificadores para la imagen y el lienzo).

cagdas_ucar
fuente
Porque no es ampliamente compatible caniuse.com/#search=createImageBitmap
Matt
createImageBitmap es compatible con el 73% de todos los usuarios. Dependiendo de su caso de uso, puede ser lo suficientemente bueno. Es solo Safari el que se niega a agregarle soporte. Creo que vale la pena mencionarlo como posible solución.
cagdas_ucar
Buena solución, pero desafortunadamente no funciona en
Firefox
1

Escribí una pequeña utilidad js para recortar y cambiar el tamaño de la imagen en el front-end. Aquí hay un enlace sobre el proyecto GitHub. También puede obtener blob de la imagen final para enviarlo.

import imageSqResizer from './image-square-resizer.js'

let resizer = new imageSqResizer(
    'image-input',
    300,
    (dataUrl) => 
        document.getElementById('image-output').src = dataUrl;
);
//Get blob
let formData = new FormData();
formData.append('files[0]', resizer.blob);

//get dataUrl
document.getElementById('image-output').src = resizer.dataUrl;
Diyaz Yakubov
fuente