img - Html5 lienzo drawImage: cómo aplicar antialiasing
new image javascript canvas (6)
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 2x2) con el elemento canvas en lugar de bicúbico (muestreo 4x4) 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 objetivo (¡gracias a Joe Mabel por mencionar esto!).
Usar escalamiento directo como en la pregunta original:
Usando la reducción como se muestra a continuación:
En este caso, deberá desviarse en 3 pasos:
En el paso 1 reducimos la imagen a la mitad utilizando un lienzo 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 lienzo 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 lienzo 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 objetivo):
steps = Math.ceil(Math.log(sourceWidth / targetWidth) / Math.log(2))
Por favor, eche un vistazo al siguiente ejemplo:
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.
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 here .
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";
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,/, "");
}