Utilizo 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 escaldada 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 reducir 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 cambió de tamaño con Photoshop:
La imagen redimensionada en lienzo:
Editar:
Traté de reducir la escala en más de un paso como se propuso en:
Cambiar el tamaño de una imagen en un lienzo HTML5 y un lienzo Html5 drawImage: cómo aplicar el 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);
}
Aquí está el resultado si uso un tamaño de 2 pasos:
Aquí está el resultado si uso un tamaño de 3 pasos:
Aquí está el resultado si uso un tamaño de 4 pasos:
Aquí está el resultado si uso un tamaño reducido 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 difusa se volverá la imagen.
¿Hay alguna manera de resolver el problema de que la imagen se vuelve más borrosa a medida que agrega más pasos?
Editar 04-10-2013: probé el algoritmo de GameAlchemist. Aquí está el resultado en comparación con Photoshop.
Imagen de PhotoShop:
Algoritmo de GameAlchemist:
fuente
Respuestas:
Como su problema es reducir la escala de su imagen, no tiene sentido hablar de interpolación, que es crear píxeles. El problema aquí es la disminución de la resolución.
Para reducir la muestra de una imagen, necesitamos convertir cada cuadrado de p * p píxeles en la imagen original en un solo píxel en la imagen de destino.
Por motivos de rendimiento, los navegadores hacen 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 a eso: dado que el muestreo de imagen 2X es muy simple de calcular (promedio de 4 píxeles para hacer uno) y se usa para píxeles de retina / HiDPI, este caso se maneja correctamente: el navegador utiliza 4 píxeles para hacer uno-.
PERO ... si usa varias veces una disminución de muestreo 2X, enfrentará el problema de que los errores de redondeo sucesivos agregarán demasiado ruido.
Lo que es peor, no siempre cambiará el tamaño por una potencia de dos, y cambiar el tamaño a la potencia más cercana + un último cambio de tamaño es muy ruidoso.
Lo que busca es una disminución de resolución de píxeles perfecta, es decir: un nuevo muestreo de la imagen que tendrá en cuenta todos los píxeles de entrada, sea cual sea la escala.
Para hacerlo, 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á justo dentro de los píxeles de destino, se superpone a 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 vs mi escala perfecta de píxeles en una escala 1/3 de un zombat.
Tenga en cuenta que la imagen se puede escalar en su navegador y está SO .jpegized.
Sin embargo, vemos que hay mucho menos ruido, especialmente en la hierba detrás del wombat y 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 correcta es menos pegadiza pero definitivamente más agradable.
Aquí está el código para hacer la reducción de escala perfecta de píxeles:
resultado del violín: http://jsfiddle.net/gamealchemist/r6aVp/embedded/result/
mismo violín: http://jsfiddle.net/gamealchemist/r6aVp/
Es bastante codicioso de 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 de origen se usa sea cual sea el tamaño de destino, y tenemos que pagar por getImageData / putImageDate, también bastante lento.
Pero no hay forma de ser más rápido que procesar cada valor de origen en este caso, y la situación no es tan mala: para mi imagen 740 * 556 de un wombat, el procesamiento tarda entre 30 y 40 ms.
fuente
Nuevo muestreo de lienzo rápido 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
fuente
Sugerencia 1: extienda la tubería de proceso
Puede usar el paso hacia abajo como lo describo en los enlaces a los que se refiere, pero parece que los usa de manera incorrecta.
No es necesario reducir la escala para escalar imágenes a relaciones superiores a 1: 2 (normalmente, pero no limitado a). Es donde necesita hacer una reducción drástica drástica , debe dividirla en dos (y raramente, más) pasos dependiendo del contenido de la imagen (en particular, donde ocurren frecuencias altas como líneas finas).
Cada vez que muestrea una imagen, perderá detalles e información. No puede esperar que la imagen resultante sea tan clara como la original.
Si luego está reduciendo las imágenes en muchos pasos, perderá mucha información en total y el resultado será pobre como ya lo notó.
Intente con solo un paso adicional, o como máximo dos.
Convoluciones
En el caso de Photoshop, observe que aplica una convolución después de que la imagen haya sido muestreada nuevamente, como la nitidez. No se trata solo de una interpolación bicúbica, por lo que, para emular completamente Photoshop, también debemos agregar los pasos que Photoshop está realizando (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 más precisa para mejorar la calidad como un proceso posterior (vea la demostración al final).
Aquí hay un código para agregar un filtro de enfoque (se basa en un filtro de convolución genérico; puse la matriz de peso para enfocar dentro de él y un factor de mezcla para ajustar la pronunciación del efecto):
Uso:
los
mixFactor
es un valor entre [0.0, 1.0] y le permite minimizar el efecto de nitidez - regla general: cuanto menor sea el tamaño, menor será el efecto necesario.Función (basada en este fragmento ):
El resultado de usar esta combinación será:
DEMO EN LÍNEA AQUÍ
Dependiendo de la cantidad de nitidez que desee agregar a la mezcla, puede obtener el resultado de "borroso" predeterminado a muy nítido:
Sugerencia 2: implementación de algoritmos de bajo nivel
Si desea obtener el mejor resultado en cuanto a calidad, deberá ir a un nivel bajo y considerar implementar, por ejemplo, este nuevo algoritmo para hacerlo.
Ver Downsampling de imagen 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 espera una mano completa si quieres lanzarte a esta tarea.
La esencia es (extractos del artículo):
Resumen
(vea el enlace provisto para todos los detalles, fórmulas, etc.)
fuente
Si desea utilizar solo el lienzo, el mejor resultado será con múltiples pasos hacia abajo. Pero eso aún no es suficiente. Para una mejor calidad, necesita una implementación js pura. Acabamos de lanzar pica : reductor de alta velocidad con calidad / velocidad variable. En resumen, redimensiona 1280 * 1024px en ~ 0.1s, y 5000 * 3000px en 1s, con la más alta calidad (filtro lanczos con 3 lóbulos). Pica tiene demo , donde puedes jugar con tus imágenes, niveles de calidad e incluso probarlo en dispositivos móviles.
Pica aún no tiene máscara de enfoque, pero eso se agregará muy pronto. Eso es mucho más fácil que implementar un filtro de convolución de alta velocidad para cambiar el tamaño.
fuente
¿Por qué usar el lienzo para cambiar el tamaño de las imágenes? Todos 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, altura 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í:
Luego, solo 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.
Pegue el siguiente código en la consola de esta página y observe lo que les sucede a los gravatares:
fuente
No es la respuesta correcta para las personas que realmente necesitan cambiar el tamaño de la imagen en sí, sino solo para reducir el tamaño del archivo .
Tuve un problema con las imágenes "directamente desde la cámara", que mis clientes a menudo cargaban en JPEG "sin comprimir".
No tan conocido es que el lienzo admite (en la mayoría de los navegadores 2017) cambiar la calidad de JPEG
Con este truco podría reducir las fotos de 4k x 3k con> 10Mb a 1 o 2Mb, seguro que depende de sus necesidades.
mira aquí
fuente
Aquí hay un servicio angular reutilizable para cambiar el tamaño de la imagen / lienzo de alta calidad: https://gist.github.com/fisch0920/37bac5e741eaec60e983
El servicio admite la convolución de lanczos y la reducción gradual gradual. El enfoque de convolución es de mayor calidad a costa de ser más lento, mientras que el enfoque de reducción gradual gradual produce resultados razonablemente antialias y es significativamente más rápido.
Ejemplo de uso:
fuente
Este es el filtro de cambio de tamaño Hermite mejorado que utiliza 1 trabajador para que la ventana no se congele.
https://github.com/calvintwr/blitz-hermite-resize
fuente
Encontré una solución que no necesita acceder directamente a los datos de píxeles y recorrerlos para realizar el muestreo. Dependiendo del tamaño de la imagen, esto puede requerir muchos recursos y sería mejor utilizar los algoritmos internos del navegador.
La función drawImage () está utilizando un método de remuestreo de interpolación lineal y vecino más cercano. Eso funciona bien cuando no redimensiona más de la mitad del tamaño original .
Si realiza un bucle para cambiar el tamaño solo la mitad a la vez, los resultados serían bastante buenos y mucho más rápidos que acceder a los datos de píxeles.
Esta función reduce la muestra a la mitad a la vez hasta alcanzar el tamaño deseado:
fuente
Tal vez hombre, puedes probar esto, que siempre uso en mi proyecto. De esta manera, no solo puedes obtener imágenes de alta calidad, sino cualquier otro elemento en tu lienzo.
fuente
en lugar de .85 , si agregamos 1.0 . Obtendrá la respuesta exacta.
Puede obtener una imagen clara y brillante. por favor, compruebe
fuente
Realmente trato de evitar ejecutar datos de imagen, especialmente en imágenes más grandes. Por lo tanto, se me ocurrió una forma bastante simple de reducir decentemente el tamaño de la imagen sin restricciones ni limitaciones mediante unos pocos pasos adicionales. Esta rutina se reduce al medio paso más bajo posible antes del tamaño objetivo deseado. Luego lo escala hasta el doble del tamaño objetivo y luego la mitad nuevamente. Suena divertido al principio, pero los resultados son asombrosamente buenos y llegan rápidamente.
fuente
context.scale(xScale, yScale)
fuente
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/
En el proyecto GitHub se puede encontrar el código completo de la demostración y el método TypeScript que quizás desee utilizar en su código.
https://github.com/eyalc4/ts-image-resizer
Este es el código final:
fuente