Pregunta ¿Por qué dibujar una línea de menos de 1,5 píxeles de grosor dos veces más lenta que dibujar una línea de 10 píxeles de grosor?


Solo estoy jugando con FireMonkey para ver si la pintura gráfica es más rápida que GDI o Graphics32 (mi biblioteca preferida en este momento).

Para ver qué tan rápido es, realicé algunas pruebas, pero encuentro un comportamiento extraño:

Dibujar líneas delgadas (<1.5 píxeles de ancho) parece ser extremadamente lento en comparación con las líneas más gruesas: Performance

  • Eje vertical: marcas de cpu para pintar 1000 líneas
  • Eje horizontal: tickness de línea *

Los resultados son bastante estables; el dibujo siempre se vuelve mucho más rápido una vez que el grosor de la línea es más de 1 píxel de ancho.

En otras bibliotecas parece que hay algoritmos rápidos para líneas individuales, y las líneas gruesas son más lentas porque primero se crea un polígono, entonces ¿por qué FireMonkey es al revés?

Necesito líneas de un solo píxel, por lo que ¿Debo pintar líneas de una manera diferente, tal vez?

Las pruebas se realizaron con este código:

// draw random lines, and copy result to clipboard, to paste in excel
procedure TForm5.PaintBox1Paint(Sender: TObject; Canvas: TCanvas);
var
  i,iWidth:Integer;
  p1,p2: TPointF;
  sw:TStopWatch;
const
  cLineCount=1000;
begin
  Memo1.Lines.Clear;
  // draw 1000 different widths, from tickness 0.01 to 10
  for iWidth := 1 to 1000 do
  begin
    Caption := IntToStr(iWidth);
    Canvas.BeginScene;
    Canvas.Clear(claLightgray);
    Canvas.Stroke.Kind := TBrushKind.bkSolid;
    Canvas.Stroke.Color := $55000000;
    Canvas.StrokeThickness :=iWidth/100;
    sw := sw.StartNew;
    // draw 1000 random lines
    for I := 1 to cLineCount do
    begin
      p1.Create(Random*Canvas.Width,Random*Canvas.Height);
      p2.Create(Random*Canvas.Width,Random*Canvas.Height);
      Canvas.DrawLine(p1,p2,0.5);
    end;
    Canvas.EndScene;
    sw.Stop;
    Memo1.Lines.Add(Format('%f'#9'%d', [Canvas.StrokeThickness,  Round(sw.ElapsedTicks / cLineCount)]));
  end;
  Clipboard.AsText := Memo1.Text;
end;

Actualizar

@Steve Wellens: De hecho, las líneas verticales y horizontales son mucho más rápidas. En realidad, hay una diferencia entre las horizontales y las verticales:

Difference between Diagonal, Horitonzal and Vertical lines Líneas diagonales: azul, líneas horizontales: verde, líneas verticales: rojo

Con las líneas verticales, hay una gran diferencia entre las líneas que tienen menos de 1 píxel de ancho. Con las líneas diagonales hay una pendiente entre 1.0 y 1.5.

Lo extraño es que apenas hay diferencia entre pintar una línea horizontal de 1 píxel y pintar uno de 20 píxeles. Supongo que aquí es donde la aceleración del hardware comienza a marcar la diferencia.


32
2018-01-23 00:50


origen


Respuestas:


Resumen: las líneas de grosor de subpíxeles antialiasing son un trabajo arduo y requieren una serie de trucos sucios para producir lo que intuitivamente esperamos ver.

El esfuerzo adicional que estás viendo es casi seguro debido al antialiasing. Cuando el grosor de la línea es inferior a un píxel y la línea no se sienta de lleno en el centro de una fila de píxeles del dispositivo, cada píxel dibujado para la línea será un píxel de brillo parcial. Para asegurarse de que esos valores parciales son lo suficientemente brillantes como para que la línea no desaparezca, se requiere más trabajo.

Como las señales de video operan en un barrido horizontal (piense en CRT, no en LCD), las operaciones de gráficos tradicionalmente se enfocan en procesar cosas, una línea de escaneo horizontal a la vez.

Aquí está mi suposición:

Para resolver ciertos problemas persistentes, los rasterizadores a veces "empujan" las líneas para que más píxeles virtuales se alineen con los píxeles del dispositivo. Si una línea horizontal de 0,25 píxeles de espesor está exactamente a medio camino entre la línea de exploración A y B del dispositivo, esa línea puede desaparecer por completo porque no se registra lo suficiente como para iluminar ningún píxel en la línea A o B. Por lo tanto, el rasterizador podría empujar línea "hacia abajo" un poco en las coordenadas virtuales para que se alinee con los píxeles del dispositivo B de la línea de exploración y produzca una línea horizontal agradablemente iluminada.

Lo mismo se puede hacer para las líneas verticales, pero probablemente no lo sea si su tarjeta gráfica / controlador está hiperfocalizado en las operaciones de escaneo horizontal (como muchas otras).

Por lo tanto, en este escenario, una línea horizontal se renderizaría muy rápido porque no se realizaría ningún antialiasing, y todo se puede hacer en una línea de escaneo.

Una línea vertical requeriría un análisis antialiasing para cada línea de exploración horizontal que cruza la línea. El rasterizador puede tener un caso especial para líneas verticales para considerar solo los píxeles izquierdo y derecho para calcular los valores de antialiasing.

Una línea diagonal no tiene atajos. Tiene jaggies en todas partes, por lo que hay mucho trabajo antialiasing para hacer en todas partes. El cálculo antialias debe considerar (submuestra) una matriz completa de puntos (al menos 4, probablemente 8) alrededor del punto objetivo para decidir cuánto de un valor parcial le dará al dispositivo píxel. La matriz se puede simplificar o eliminar por completo para líneas verticales u horizontales, pero no para diagonales.

Hay un elemento adicional que realmente solo es una preocupación para las líneas de grosor subpíxel: ¿cómo evitamos que la línea de grosor subpixel desaparezca por completo o que haya lagunas notables donde la línea no cruza el centro del píxel de un dispositivo? Es probable que después de que los valores antialias se calculen en una línea de exploración, si no hay una "señal" clara o un píxel del dispositivo suficientemente iluminado causado por la línea virtual, el rasterizador debe retroceder e "intentar más" o aplicar alguna heurística de refuerzo a obtener una relación señal / piso más fuerte para que los píxeles del dispositivo que representan la línea virtual sean tangibles y continuos.

Dos píxeles de dispositivos adyacentes con un 40% de brillo están bien. Si la única salida de rasterizador para la línea de exploración es dos píxeles adyacentes al 5%, el ojo percibirá un espacio en la línea. No está bien.

Cuando la línea tiene más de 1.5 píxeles de dispositivo de grosor, siempre tendrá al menos un píxel de dispositivo bien iluminado en cada línea de exploración y no tendrá que retroceder e intentar más.

¿Por qué 1.5 es el número mágico para el grosor de línea? Pregúntele a Pitágoras. Si el píxel de su dispositivo tiene 1 unidad de ancho y alto, entonces la longitud de la diagonal del píxel del dispositivo cuadrado es sqrt (1 ^ 2 + 1 ^ 2) = sqrt (2) = 1.41ish. Cuando el grosor de su línea es mayor que la longitud de la diagonal del píxel de un dispositivo, siempre debe tener al menos un píxel "bien iluminado" en la salida del escáner, sin importar el ángulo de la línea.

Esa es mi teoría, de todos modos.


28
2018-01-24 01:43



En otras bibliotecas parece que hay algoritmos rápidos para líneas individuales, y las líneas gruesas son más lentas porque primero se crea un polígono, entonces ¿por qué FireMonkey es al revés?

En Graphics32, el algoritmo de línea de Bresenham se usa para acelerar las líneas que se dibujan con un ancho de 1px y que sin duda debería ser rápido. FireMonkey no tiene su propio rasterizador nativo, sino que delega las operaciones de pintura a otras API (en Windows, se delegará en Direct2D o GDI +).

Lo que está observando es, de hecho, el rendimiento del rasterizador Direct2D y puedo confirmar que ya hice observaciones similares (he comparado muchos rasterizadores diferentes). Aquí hay una publicación que habla específicamente sobre el rendimiento del rasterizador Direct2D (por cierto , no es una regla general que las líneas delgadas se dibujen más lentamente, especialmente no en mi propio rasterizador):

http://www.graphics32.org/news/newsgroups.php?article_id=10249

Como se puede ver en el gráfico, Direct2D tiene muy buen rendimiento para elipses y líneas gruesas, pero peformance mucho peor en los otros puntos de referencia (donde mi propio rasterizador es más rápido).

Necesito líneas de un píxel, ¿debería pintar líneas de una manera diferente?

Implementé un nuevo backend FireMonkey (un nuevo descendiente TCanvas), que se basa en mi propio motor rasterizador VPR. Debería ser más rápido que Direct2D para líneas finas y para texto (aunque está usando técnicas de rasterización poligonal). Todavía puede haber algunas advertencias que deben abordarse para que funcione 100% sin problemas como un backend Firemonkey. Más información aquí:

http://graphics32.org/news/newsgroups.php?article_id=11565


6
2018-01-26 01:52