fillrect javascript html5 canvas

javascript - fillrect - ¿Cómo puedo mejorar mi calidad de paleta Html5 Canvas?



canvas html5 (5)

El problema

Este es uno de esos casos en los que es casi imposible obtener un resultado sin problemas sin ajustarlo manualmente.

La causa tiene que ver con el espacio mínimo para distribuir píxeles suavizados. En este caso, solo tenemos una altura de píxel única entre cada sección en la curva cuadrática.

Si miramos una curva sin suavizar podemos ver más claramente esta limitación (sin suavizar cada píxel se encuentra en una posición entera):

La línea roja indica una sola sección y podemos ver que la transición entre la sección anterior y la siguiente debe distribuirse en la altura de un píxel. Vea mi respuesta aquí para saber cómo funciona esto.

El suavizado se basa en la fracción restante para la conversión de coordenadas del punto a entero. Dado que el suavizado usa esta fracción para determinar el color y el alfa en función del color principal y el color de fondo para agregar un píxel sombreado, rápidamente nos toparemos con limitaciones ya que cada píxel utilizado para suavizar ocupa todo un píxel y debido a la falta de espacio como aquí, las sombras serán muy ásperas y, por lo tanto, reveladoras.

Cuando una línea larga va de y a y +/- 1 (o x a x +/- 1) no hay un solo píxel entre los puntos finales que aterrice en un límite perfecto, lo que significa que cada píxel entre es en cambio un matiz.

Si observamos más de cerca algunos segmentos de la línea actual, podemos ver los tonos más claramente y cómo afecta el resultado:

Adicionalmente

Aunque esto explica el principio en general - Otros problemas son (como apenas se insinuó en la revisión 1 (último párrafo) de esta respuesta hace unos días, pero eliminados y se me olvidó profundizar en ello) es que las líneas dibujadas en la parte superior de cada uno otros en general, contribuirán al contraste ya que los píxeles alfa se mezclarán y en algunas partes introducirán un mayor contraste.

Tendrá que repasar el código para eliminar trazos innecesarios para que obtenga un solo trazo en cada ubicación. Tiene, por ejemplo, algunos closePaths() que conectarán el final de la ruta con el comienzo y dibujarán líneas dobles, y así sucesivamente.

Una combinación de estos dos debe dar un buen equilibrio entre suave y nítida.

Banco de prueba de suavizado

Esta demostración le permite ver el efecto de cómo se distribuye el suavizado en función del espacio disponible.

Cuanto más doblada es la curva, más corta se vuelve cada sección y requeriría menos alisamiento. El resultado: línea más suave.

var ctx = c.getContext("2d"); ctx.imageSmoothingEnabled = ctx.mozImageSmoothingEnabled = ctx.webkitImageSmoothingEnabled = false; // for zoom! function render() { ctx.clearRect(0, 0, c.width, c.height); !!t.checked ? ctx.setTransform(1,0,0,1,0.5,0.5):ctx.setTransform(1,0,0,1,0,0); ctx.beginPath(); ctx.moveTo(0,1); ctx.quadraticCurveTo(150, +v.value, 300, 1); ctx.lineWidth = +lw.value; ctx.strokeStyle = "hsl(0,0%," + l.value + "%)"; ctx.stroke(); vv.innerHTML = v.value; lv.innerHTML = l.value; lwv.innerHTML = lw.value; ctx.drawImage(c, 0, 0, 300, 300, 304, 0, 1200, 1200); // zoom } render(); v.oninput=v.onchange=l.oninput=l.onchange=t.onchange=lw.oninput=render;

html, body {margin:0;font:12px sans-serif}; #c {margin-top:5px}

<label>Bend: <input id=v type=range min=1 max=290 value=1></label> <span id=vv></span><br> <label>Lightness: <input id=l type=range min=0 max=60 value=0></label> <span id=lv></span><br> <label>Translate 1/2 pixel: <input id=t type=checkbox></label><br> <label>Line width: <input id=lw type=range min=0.25 max=2 step=0.25 value=1></label> <span id=lwv></span><br> <canvas id=c width=580></canvas>

Solución

No hay una buena solución a menos que la resolución se haya incrementado. Así que estamos atascados con ajustar los colores y la geometría para dar un resultado más suave.

Podemos usar algunos de los trucos para moverte:

  1. Podemos reducir el ancho de la línea a 0.5 - 0.75 por lo que obtenemos un degradado de color menos visible utilizado para el sombreado.
  2. Podemos atenuar el color para disminuir el contraste
  3. Podemos traducir medio píxel. Esto funcionará en algunos casos, otros no.
  4. Si la nitidez no es esencial, aumentar el ancho de la línea puede ayudar a equilibrar los tonos. El valor de ejemplo podría ser 1.5 combinado con un color más claro / gris.
  5. Podríamos usar la sombra también, pero este es un enfoque que requiere mucho rendimiento ya que usa más memoria y desenfoque gaussiano, junto con dos pasos de composición adicionales y es relativamente lento. Yo recomendaría usar 4) en su lugar.

1) y 2) están algo relacionados ya que al usar un ancho de línea <1 forzará el subpíxel en toda la línea, lo que significa que ningún píxel es negro puro. El objetivo de ambas técnicas es reducir el contraste para camuflar los degradados de sombra dando la ilusión de ser una línea más fina / más fina.

Tenga en cuenta que 3) solo mejorará los píxeles que, como resultado, aterrizan en un límite exacto de píxeles. Todos los demás casos seguirán borrosos. En este caso, este truco tendrá poco o ningún efecto en la curva, pero sirve bien para los rectángulos y las líneas verticales y horizontales.

Si aplicamos estos trucos utilizando el banco de pruebas anterior, obtendremos algunos valores utilizables:

Variación 1

ctx.lineWidth = 1.25; // gives some space for lightness ctx.strokeStyle = "hsl(0,0%,50%)"; // reduces contrast ctx.setTransform(1,0,0,1,0.5,0.5); // not so useful for the curve itself

Variación 2 :

ctx.lineWidth = 0.5; // sub-pixels all points ctx.setTransform(1,0,0,1,0.5,0.5); // not so useful for the curve itself

Podemos afinar aún más experimentando con el ancho de línea y el color / ligereza del trazo.

Una alternativa es producir un resultado más preciso para las curvas usando Photoshop o algo similar que tenga mejores algoritmos de suavizado, y usar eso como imagen en lugar de usar curva nativa.

He estado escribiendo un pequeño complemento de JavaScript, y estoy teniendo un pequeño problema para mejorar la calidad global del lienzo. He buscado en la web aquí y allá pero no encuentro nada que tenga sentido.

Las líneas creadas a partir de mis curvas NO son suaves, si miras el jsfiddle a continuación entenderás lo que quiero decir. De alguna manera parece pixelado. ¿Hay alguna manera de mejorar la calidad? ¿O hay un Canvas Framework que ya usa algún método para mejorar automáticamente su calidad que puedo usar en mi proyecto?

Mi lienzo Render

No estoy seguro de si esto ayuda, pero estoy usando este código al comienzo de mi script:

var c = document.getElementsByClassName("canvas"); for (i = 0; i < c.length; i++) { var canvas = c[i]; var ctx = canvas.getContext("2d"); ctx.clearRect(0,0, canvas.width, canvas.height); ctx.lineWidth=1; } }

Gracias por adelantado

Ejemplo de mi código de curva:

var data = { diameter: 250, slant: 20, height: 290 }; for (i = 0; i < c.length; i++) { var canvas = c[i]; var ctx = canvas.getContext("2d"); ctx.beginPath(); ctx.moveTo( 150 + ((data.diameter / 2) + data.slant ), (data.height - 3) ); ctx.quadraticCurveTo( 150 , (data.height - 15), 150 - ((data.diameter / 2) + data.slant ), (data.height - 3)); ctx.lineTo( 150 - ((data.diameter / 2) + data.slant ), data.height ); ctx.quadraticCurveTo( 150 , (data.height + 5), 150 + ((data.diameter / 2) + data.slant ), data.height); ctx.closePath(); ctx.stroke(); }


Anti-aliasing ayuda mucho. Pero cuando tiene líneas en ángulo que están cerca de la horizontal, o vertical, o se curvan suavemente, el anti-aliasing va a ser mucho más notable. Especialmente para líneas delgadas con anchos de menos de un par de píxeles más o menos.

Como señala Maciej, si tiene una línea de alrededor de 1px de ancho y pasa directamente entre dos píxeles, el anti-aliasing dará como resultado una línea de dos píxeles de ancho y medio gris.

Puede que tengas que aprender a vivir con eso. Solo hay mucho que puede hacer el anti-aliasing.


Esta pregunta me pareció un poco extraña. La reproducción de lienzos, aunque no es la mejor en comparación con los renderizadores de gama alta, sigue siendo muy buena. Entonces, ¿por qué hay un problema con este ejemplo? Estaba a punto de dejarlo, pero vale la pena mirar 500 puntos. De eso puedo dar dos consejos, una solución y una alternativa.

En primer lugar, los diseñadores y sus diseños deben incorporar los límites de los medios. Puede sonar un poco presuntuoso, pero está tratando de reducir lo irreductible, no puede deshacerse de alias en una pantalla de mapa de bits.

En segundo lugar, siempre escriba un buen código bien comentado. Aquí hay 4 respuestas y nadie eligió el error. Eso es porque el código presentado es bastante complicado y difícil de entender. Estoy adivinando (casi como yo) que los demás se saltaron el código por completo en lugar de averiguar qué estaba haciendo mal.

Tenga en cuenta

La calidad de las imágenes en todas las respuestas a esta pregunta puede ser escalada (por lo tanto, remuestreada) por el navegador. Para hacer una verdadera comparación, es mejor ver las imágenes en una página separada para que no se escalen.

Resultados del estudio del problema en orden de calidad (en mi opinión)

Algoritmo genético

El mejor método que encontré para mejorar la calidad, un método que normalmente no está asociado a gráficos de computadora, es usar una forma muy simple de algoritmo genético (GA) para buscar la mejor solución haciendo cambios sutiles en el proceso de renderizado.

El posicionamiento de sub píxeles, ancho de línea, selección de filtro, composición, remuestreo y cambios de color pueden hacer cambios marcados en el resultado final. Este presente billones de posibles combinaciones, cualquiera de las cuales podría ser la mejor. Los GA son muy adecuados para encontrar soluciones a este tipo de búsquedas, aunque en este caso la prueba de aptitud física fue problemática, porque la calidad es subjetiva, la prueba de aptitud también debe serlo y, por lo tanto, requiere la participación humana.

Después de muchas iteraciones y de dedicar un poco más de mi tiempo de lo que quería, encontré un método muy adecuado para este tipo de imágenes (muchas líneas delgadas y poco espaciadas). El GA no es adecuado para su publicación. Afortunadamente, solo estamos interesados ​​en la solución más adecuada y GA creó una secuencia de pasos que son repetibles y consistentes para el estilo particular que se ejecutó para resolver.

El resultado de la búsqueda GA es.

ver la Nota 1 para los pasos de procesamiento

Los resultados son mucho mejores de lo que esperaba, así que tuve que tener una mirada cerrada y noté dos características distintas que diferencian esta imagen de todas las demás presentadas en esta y otras respuestas.

  • Anti-aliasing no es uniforme. Donde normalmente esperaría un cambio uniforme en la intensidad, este método produce un gradiente escalonado (por qué esto hace que se vea mejor, no lo sé)
  • Nodos oscuros. Justo donde la transición de una fila a la siguiente está casi completa, la línea inferior o superior se vuelve notablemente más oscura por unos pocos píxeles y luego vuelve a la sombra más clara cuando la línea se ajusta a la fila. Esto sirve para compensar el aligeramiento de la línea general, ya que comparte su intencidad en dos filas.

Esto me ha dado algo de reflexión y veré si estas características se pueden incorporar directamente en la línea y en la representación de líneas de escaneo de curvas.

Nota 1 Los métodos utilizados para representar la imagen de arriba. Tamaño de lienzo fuera de pantalla de 1200 por 1200. ctx.setTransform(4,0,0,4,0,0) a escala 4 ctx.setTransform(4,0,0,4,0,0) , pixel y offset 3 ctx.translate(0,3) , Ancho de línea 0.9pixels renderizado dos veces en blanco fondo, borroso de fotones de 1 píxel borrado repetido 3 veces (similar a convolución 3 * 3 desenfoque gaussiano pero un poco menos de peso a lo largo de las diagonales), 4 veces muestras abajo mediante muestreo de 2 pasos usando medios de recuento de fotones (cada píxel es la raíz cuadrada de la media de los cuadrados de los 4 (2 por 2) píxeles muestreados). Afinar una pasada (desafortunadamente se trata de un filtro de afilado personalizado muy complejo (como algunos filtros de afilado de pin / píxel)) y luego ctx.globalCompositeOperation = "multiply" una vez con ctx.globalCompositeOperation = "multiply" y ctx.globalAlpha = 0.433 luego capturarlo en el lienzo. Todo el procesamiento realizado en Firefox

Code fix

El horrible resultado de renderizado fue ocasionado por algunas incoherencias de renderización menores en tu código.

A continuación se encuentra el antes y el después de la corrección del código. Como pueden ver, hay una mejora notable.

Entonces, ¿qué hiciste mal?

No demasiado, el problema es que usted representaba líneas sobre la parte superior de las líneas existentes. Esto tiene el efecto de aumentar el contraste de esas líneas, el renderizado no sabe que usted no quiere los colores existentes y, por lo tanto, se suma al anti aliasing existente duplicando la opacidad y destruyendo el efecto.

Haz aparecer tu código solo con la representación de la forma. Los comentarios muestran los cambios.

ctx.beginPath(); // removed the top and bottom lines of the rectangle ctx.moveTo(150, 0); ctx.lineTo(150, 75); ctx.moveTo(153, 0); ctx.lineTo(153, 75); // dont need close path ctx.stroke(); ctx.beginPath(); ctx.moveTo((150 - (data.diameter / 2)), 80); ctx.quadraticCurveTo(150, 70, 150 + (data.diameter / 2), 80); ctx.lineTo(150 + (data.diameter / 2), 83); ctx.quadraticCurveTo(150, 73, 150 - (data.diameter / 2), 83); ctx.closePath(); ctx.stroke(); ctx.beginPath(); // removed the two quadratic curves that where drawing over the top of existing ones ctx.moveTo(150 + (data.diameter / 2), 83); ctx.lineTo(150 + ((data.diameter / 2) + data.slant), data.height); ctx.moveTo(150 - ((data.diameter / 2) + data.slant), data.height); ctx.lineTo(150 - (data.diameter / 2), 83); // dont need close path ctx.stroke(); ctx.beginPath(); // removed a curve ctx.moveTo(150 + ((data.diameter / 2) + data.slant), (data.height - 3)); ctx.quadraticCurveTo(150, (data.height - 15), 150 - ((data.diameter / 2) + data.slant), (data.height - 3)); // dont need close path ctx.stroke(); ctx.beginPath(); ctx.moveTo(150 + ((data.diameter / 2) + data.slant), data.height); ctx.quadraticCurveTo(150, (data.height - 10), 150 - ((data.diameter / 2) + data.slant), data.height); ctx.quadraticCurveTo(150, (data.height + 5), 150 + ((data.diameter / 2) + data.slant), data.height); ctx.closePath(); ctx.stroke();

Entonces ahora el render es mucho mejor.

Ojo subjetivo

La corrección de código en mi opinión es la mejor solución que se puede lograr con el mínimo esfuerzo. Como la calidad es subjetiva a continuación, presento varios métodos más que pueden o no mejorar la calidad, dependiendo del ojo del juez.

MUESTREO ABAJO

Otro motivo para mejorar la calidad del renderizado es reducir la muestra. Esto implica simplemente renderizar la imagen a una resolución más alta y luego renderizar la imagen a una resolución más baja. Cada píxel es entonces un promedio de 2 o más píxeles del original.

Hay muchos métodos de muestreo descendente, pero muchos no tienen ningún uso práctico debido al tiempo que tardan en procesar la imagen.

El muestreo descendente más rápido se puede realizar a través de la GPU y llamadas de representación en lienzo nativas. Simplemente cree un lienzo fuera de pantalla con una resolución 2 veces o 4 veces mayor de lo requerido, luego use la transformación para escalar la representación de la imagen (para que no tenga que cambiar el código de representación). Luego, renderiza esa imagen con la resolución requerida para el resultado.

Ejemplo de reducción de muestreo con 2D API y JS

var canvas = document.getElementById("myCanvas"); // get onscreen canvas // set up the up scales offscreen canvas var display = {}; display.width = canvas.width; display.height = canvas.height; var downSampleSize = 2; var canvasUp = document.createElement("canvas"); canvasUp.width = display.width * downSampleSize; canvasUp.height = display.height * downSampleSize; var ctx = canvasUp.getContext("2D"); ctx.setTransform(downSampleSize,0,0,downSampleSize,0,0); // call the render function and render to the offscreen canvas Once you have the image just render it to you onscreen canvas ctx = canvas.getContext("2d"); ctx.drawImage(canvasUp,0,0,canvas.width,canvas.height);

Las siguientes imágenes muestran el resultado del muestreo de 4 * hacia abajo y la variación del ancho de línea de 1.2 píxeles a 0.9 píxeles (tenga en cuenta que el ancho de la línea muestreada es 4 * que. 4.8, 4.4, 4 y 3.6)

Imagen siguiente 4 * muestra abajo usando el remuestreo de Lanczos una muestra razonablemente rápida (más adecuada para las imágenes)

El muestreo a la baja es rápido y requiere muy poca modificación del código original para funcionar. La imagen resultante mejorará el aspecto de los detalles finos y creará una línea antialias ligeramente mejor.

El muestreo descendente también permite un control mucho más fino del ancho de línea (aparente). la representación en la resolución de la pantalla da malos resultados en cambios de 1/4 de píxel en el ancho de la línea. Con la reducción de resolución, duplica y cuadruplica los detalles finos de 1/8 y 1/16 (tenga en cuenta que existen otros tipos de efectos de aliasing que entran en juego cuando se renderizan a resoluciones de subpíxeles)

Gama dinámica

El rango dinámico en medios digitales se refiere al rango de valores que los medios pueden manejar. Para el lienzo, ese rango es de 256 (8bits) por canal de color. El ojo humano tiene dificultades para elegir la diferencia entre valores concurrentes, digamos 128 y 129, por lo que este rango es casi omnipresente en el ámbito de los gráficos por computadora. La GPU moderna, sin embargo, puede renderizar a rangos dinámicos mucho más altos, 16 bits, 24 bits, 32 bits por canal e incluso doble precisión flota 64 bits.

El rango de 8 bits adecuado es bueno para el 95% de los casos, pero sufre cuando la imagen que se está procesando se fuerza a un rango dinámico más bajo. Esto sucede cuando representa una línea sobre un color que está cerca del color de la línea. En la búsqueda de que la imagen no se muestra en un fondo muy brillante (ejemplo # 888), el resultado es que el aliasing anti solo tiene un rango de 7 bits que reduce a la mitad el rango dinámico. El problema se complica por el hecho de que si la imagen se procesa en un fondo transparente donde el aliasing anti se logra variando el canal alfa, lo que resulta en la introducción de un segundo nivel de artefactos.

Cuando tiene en cuenta el rango dinámico, puede diseñar sus imágenes para obtener el mejor resultado (dentro de las limitaciones de diseño). Cuando se conoce el renderizado y el fondo, no se renderiza en un lienzo transparente, dejando que el hardware componga el resultado final de la pantalla, se renderice el fondo en el lienzo y luego se renderice el diseño. Intente mantener el rango dinámico lo más grande posible, mientras mayor sea la diferencia de color, mejor será el algoritmo de antialiasing para tratar el color intermedio.

A continuación se muestra un ejemplo de renderizado con varias intensidades de fondo, que se representan con un muestreo de 2 * hacia abajo en el fondo prestado. BG denota la intensidad de fondo.

Tenga en cuenta que esta imagen también se adapta a la página y el navegador muestra muestras, lo que agrega artefactos adicionales.

TIPO VERDADERO como

Mientras que aquí hay otro método. Si considera que la pantalla está formada por píxeles, cada píxel tiene 3 partes rojas, verdes y azules y los agrupamos siempre comenzando en rojo.

Pero no importa dónde se inicie un píxel, lo único que importa es que el píxel tenga los 3 colores rgb, podría ser gbr o brg. Cuando miras la pantalla de este modo, obtienes 3 veces la resolución horizontal con respecto a los bordes de los píxeles. El tamaño de píxel sigue siendo el mismo pero está compensado. Así es como Microsoft hace su renderizado de fuente especial (tipo verdadero) Desafortunadamente Microsoft tiene muchas patentes sobre el uso de este método, así que todo lo que puedo hacer es mostrarle cómo se ve cuando se hacen ignorar los límites de píxeles.

El efecto es más pronunciado en la resolución horizontal, y no mejora mucho la vertical (tenga en cuenta que esta es mi propia implementación de la representación de lienzo y aún se está refinando) Este método tampoco funciona para imágenes transparentes


Qué es una línea inclinada en una matriz de píxeles es lo que debes entender. Si necesita dibujar una línea inclinada con un ancho de píxel único, no hay manera de evitar que tenga bordes irregulares, ya que la inclinación se logra a través de un patrón vertical progresivo de líneas horizontales.

La solución es tener algún efecto de desenfoque alrededor de las líneas y hacer que la línea se una más suave.

shadowColor usar las shadowColor , shadowBlur , lineCap y lineJoin del contexto canvas para lograr esto.

Pon la siguiente configuración e intenta dibujar tus líneas.

for (i = 0; i < c.length; i++) { var canvas = c[i]; var ctx = canvas.getContext("2d"); ctx.shadowColor = "rgba(0,0,0,1)"; ctx.shadowBlur = 2; ctx.lineCap = ''round''; ctx.lineJoin = ''round''; ctx.lineWidth = 1; ctx.strokeStyle = ''black''; ctx.clearRect(0,0, canvas.width, canvas.height); }

Aquí está el resultado

Intente jugar con la opacidad shadowColor y el tamaño de desenfoque junto con el ancho y el color de la línea. Puedes obtener resultados bastante sorprendentes.

En una nota lateral, su proyecto me suena más SVG que Canvas. Probablemente debería pensar en pasar a SVG para obtener un mejor soporte y rendimiento de dibujo.

Actualizar

Aquí hay un ajuste fino

ctx.shadowColor = "rgba(128,128,128,.2)"; ctx.shadowBlur = 1; ctx.lineCap = ''round''; ctx.lineJoin = ''round''; ctx.lineWidth = 1; ctx.strokeStyle = ''gray'';


Una razón para las líneas blury es dibujar píxeles intermedios. Esta respuesta ofrece una buena visión general del sistema de coordenadas del lienzo:

https://.com/a/3657831/4602079

Una forma de mantener las coordenadas enteras pero aún obtener líneas nítidas es traducir el contexto por 0.5 píxeles:

context.translate(0.5,0.5);

Eche un vistazo al fragmento a continuación. El segundo lienzo se traduce por (0.5, 0.5) haciendo que la línea dibujada con coordenadas enteras parezca nítida.

Eso debería arreglar tus líneas rectas. Curvas, líneas diagonales, etc. serán antialias (píxeles grises alrededor de los trazos). No puedes hacer mucho al respecto. Cuanto mayor sea la resolución, menos visibles son y todas las líneas, excepto las rectas, se ven mejor antialias de todos modos.

function draw(ctx){ ctx.beginPath(); ctx.moveTo(25, 30); ctx.lineTo(75, 30); ctx.stroke(); ctx.beginPath(); ctx.moveTo(25, 50.5); ctx.lineTo(75, 50.5); ctx.stroke(); } draw(document.getElementById("c1").getContext("2d")) var ctx = document.getElementById("c2").getContext("2d"); ctx.translate(0.5, 0.5); draw(ctx);

<canvas id="c1" width="100" height="100" style="width: 100px; height: 100px"></canvas> <canvas id="c2" width="100" height="100" style="width: 100px; height: 100px"></canvas>