Html5 canvas drawImage: cómo aplicar antialiasing

Por favor, eche un vistazo al siguiente ejemplo:

http://jsfiddle.net/MLGr4/47/

var canvas=document.getElementById("canvas"); var ctx=canvas.getContext("2d"); img=new Image(); img.onload=function(){ canvas.width=400; canvas.height=150; ctx.drawImage(img,0,0,img.width,img.height,0,0,400,150); } img.src="http://openwalls.com/image/1734/colored_lines_on_blue_background_1920x1200.jpg"; 

Como puede ver, la imagen no está anti-alias, aunque se dice que drawImage aplica anti aliasing automáticamente. Intenté muchas maneras diferentes, pero parece que no funciona. ¿Podría decirme cómo puedo obtener una imagen antialias? Gracias.

Porque

Algunas imágenes son muy difíciles de muestrear e interpolar , como esta con curvas, cuando se quiere pasar de un tamaño grande a uno pequeño.

Aparentemente, los navegadores utilizan interpolación bi-lineal (muestreo 2×2) con el elemento canvas en lugar de bicúbico (muestreo 4×4) por razones de rendimiento (probables).

Si el paso es demasiado grande, simplemente no hay suficientes píxeles para muestrear, lo que se refleja en el resultado.

Desde una perspectiva de señal / DSP, puede ver esto como un valor umbral del filtro de paso bajo configurado demasiado alto, lo que puede dar como resultado un alias si hay muchas frecuencias altas (detalles) en la señal.

Solución

Actualización 2017: ahora hay una nueva propiedad definida en las especificaciones para establecer la calidad del remuestreo:

 context.imageSmoothingQuality = "low|medium|high" 

Actualmente solo es compatible con Chrome. Los métodos reales utilizados por nivel se dejan al vendedor para decidir, pero es razonable suponer que Lanczos es “alto” o algo equivalente en calidad. Esto significa que se puede saltear por completo, o se pueden usar pasos más grandes con menos redibujados, según el tamaño de la imagen y el navegador. Hasta entonces..:
Fin de transmisión

La solución es usar la reducción para obtener un resultado adecuado. Reducir significa que reduce el tamaño en pasos para permitir que el rango limitado de interpolación cubra suficientes píxeles para el muestreo.

Esto permitirá buenos resultados también con la interpolación bi-lineal (en realidad se comporta de forma muy similar a la bicúbica al hacer esto) y la sobrecarga es mínima ya que hay menos píxeles para muestrear en cada paso.

El paso ideal es ir a la mitad de la resolución en cada paso hasta que establezca el tamaño del objective (¡gracias a Joe Mabel por mencionar esto!).

Violín modificado

Usar escalamiento directo como en la pregunta original:

IMAGEN NORMAL ABAJO

Usando la reducción como se muestra a continuación:

IMAGEN ABAJO

En este caso, deberá desviarse en 3 pasos:

En el paso 1 reducimos la imagen a la mitad utilizando un canvas fuera de pantalla:

 /// step 1 - create off-screen canvas 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); 

El paso 2 reutiliza el canvas fuera de pantalla y dibuja la imagen reducida a la mitad otra vez:

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

Y dibujamos una vez más al canvas principal, de nuevo reducido a la mitad, pero al tamaño final:

 /// step 3 ctx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5, 0, 0, canvas.width, canvas.height); 

Propina:

Puede calcular el número total de pasos necesarios, usando esta fórmula (incluye el paso final para establecer el tamaño del objective):

 steps = Math.ceil(Math.log(sourceWidth / targetWidth) / Math.log(2)) 

Recomiendo mucho pica para esas tareas. Su calidad es superior a las reducciones múltiples y es bastante rápida al mismo tiempo. Aquí hay una demostración .

  var getBase64Image = function(img, quality) { var canvas = document.createElement("canvas"); canvas.width = img.width; canvas.height = img.height; var ctx = canvas.getContext("2d"); //----- origin draw --- ctx.drawImage(img, 0, 0, img.width, img.height); //------ reduced draw --- var canvas2 = document.createElement("canvas"); canvas2.width = img.width * quality; canvas2.height = img.height * quality; var ctx2 = canvas2.getContext("2d"); ctx2.drawImage(canvas, 0, 0, img.width * quality, img.height * quality); // -- back from reduced draw --- ctx.drawImage(canvas2, 0, 0, img.width, img.height); var dataURL = canvas.toDataURL("image/png"); return dataURL; // return dataURL.replace(/^data:image\/(png|jpg);base64,/, ""); } 

Como complemento a la respuesta de Ken, aquí hay otra solución para realizar la disminución de muestreo en mitades (para que el resultado se vea bien usando el algoritmo del navegador):

  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

Creé un servicio angular reutilizable para manejar el redimensionamiento de imágenes de alta calidad para cualquiera que esté interesado: https://gist.github.com/fisch0920/37bac5e741eaec60e983

El servicio incluye el enfoque de downscaling paso a paso de Ken, así como una versión modificada del enfoque de la convolución lanczos que se encuentra aquí .

Incluí ambas soluciones porque ambas tienen sus propios pros / contras. El enfoque de la convolución lanczos es de mayor calidad a costa de ser más lento, mientras que el enfoque de reducción 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 }) }) 

En caso de que alguien más esté buscando todavía una respuesta, hay otra manera en la que puede usar la imagen de fondo en lugar de drawImage (). No perderá ninguna calidad de imagen de esta manera.

JS:

  var canvas=document.getElementById("canvas"); var ctx=canvas.getContext("2d"); var url = "http://openwalls.com/image/17342/colored_lines_on_blue_background_1920x1200.jpg"; img=new Image(); img.onload=function(){ canvas.style.backgroundImage = "url(\'" + url + "\')" } img.src="http://openwalls.com/image/17342/colored_lines_on_blue_background_1920x1200.jpg"; 

demostración de trabajo

    Intereting Posts