No es muy difícil implementar el algoritmo de renderizado de líneas anti-aliasing de Xiaolin Wu en Delphi. Solía the Wikipedia article como referencia cuando escribí el siguiente procedimiento (en realidad, yo sólo traduje el pseudo-código para Delphi y ha corregido un error, y ha añadido soporte para un fondo de color):
procedure DrawAntialisedLine(Canvas: TCanvas; const AX1, AY1, AX2, AY2: real; const LineColor: TColor);
var
swapped: boolean;
procedure plot(const x, y, c: real);
var
resclr: TColor;
begin
if swapped then
resclr := Canvas.Pixels[round(y), round(x)]
else
resclr := Canvas.Pixels[round(x), round(y)];
resclr := RGB(round(GetRValue(resclr) * (1-c) + GetRValue(LineColor) * c),
round(GetGValue(resclr) * (1-c) + GetGValue(LineColor) * c),
round(GetBValue(resclr) * (1-c) + GetBValue(LineColor) * c));
if swapped then
Canvas.Pixels[round(y), round(x)] := resclr
else
Canvas.Pixels[round(x), round(y)] := resclr;
end;
function rfrac(const x: real): real; inline;
begin
rfrac := 1 - frac(x);
end;
procedure swap(var a, b: real);
var
tmp: real;
begin
tmp := a;
a := b;
b := tmp;
end;
var
x1, x2, y1, y2, dx, dy, gradient, xend, yend, xgap, xpxl1, ypxl1,
xpxl2, ypxl2, intery: real;
x: integer;
begin
x1 := AX1;
x2 := AX2;
y1 := AY1;
y2 := AY2;
dx := x2 - x1;
dy := y2 - y1;
swapped := abs(dx) < abs(dy);
if swapped then
begin
swap(x1, y1);
swap(x2, y2);
swap(dx, dy);
end;
if x2 < x1 then
begin
swap(x1, x2);
swap(y1, y2);
end;
gradient := dy/dx;
xend := round(x1);
yend := y1 + gradient * (xend - x1);
xgap := rfrac(x1 + 0.5);
xpxl1 := xend;
ypxl1 := floor(yend);
plot(xpxl1, ypxl1, rfrac(yend) * xgap);
plot(xpxl1, ypxl1 + 1, frac(yend) * xgap);
intery := yend + gradient;
xend := round(x2);
yend := y2 + gradient * (xend - x2);
xgap := frac(x2 + 0.5);
xpxl2 := xend;
ypxl2 := floor(yend);
plot(xpxl2, ypxl2, rfrac(yend) * xgap);
plot(xpxl2, ypxl2 + 1, frac(yend) * xgap);
for x := round(xpxl1) + 1 to round(xpxl2) - 1 do
begin
plot(x, floor(intery), rfrac(intery));
plot(x, floor(intery) + 1, frac(intery));
intery := intery + gradient;
end;
end;
Para utilizar esta función, simplemente proporcione el lienzo para dibujar (de una manera bastante similar a las funciones de Windows GDI que requieren un contexto de dispositivo (DC)) y especifique los puntos iniciales y finales en la línea. Observe que el código anterior dibuja una línea negra, y que el fondo tiene que ser blanco. No es difícil generalizar esto en cualquier situación, ni siquiera dibujos con transparencia alfa. Simplemente ajuste la función plot
, en la cual c \in [0, 1]
es opacidad del píxel en (x, y)
.
Ejemplo de uso:
Crear un nuevo proyecto VCL y añadir
procedure TForm1.FormCreate(Sender: TObject);
begin
Canvas.Brush.Style := bsSolid;
Canvas.Brush.Color := clWhite;
end;
procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
begin
Canvas.FillRect(ClientRect);
DrawAntialisedLine(Canvas, Width div 2, Height div 2, X, Y, clBlack);
end;
Click to magnify http://privat.rejbrand.se/aaline.png
(Magnify)
OpenGL
Si necesita alto rendimiento y alta calidad de representación en 2D o 3D , y usted hace todo el dibujo usted mismo, entonces OpenGL es generalmente la mejor opción. Es muy fácil escribir una aplicación OpenGL en Delphi. Ver http://privat.rejbrand.se/smooth.exe para un ejemplo que hice en solo diez minutos. Use el botón derecho del mouse para alternar entre polígonos y contornos rellenos, y haga clic y mantenga presionado el botón izquierdo del mouse para disparar.
actualización
que acaba de hacer el trabajo de código en un fondo de color, por ejemplo, una fotografía.
Click to magnify http://privat.rejbrand.se/aabkg.png
(Magnify)
Actualización - El método ultrarrápido
El código anterior es bastante lento porque la propiedad Bitmap.Pixels
es increíblemente lento. Cuando trabajo con gráficos, siempre represento un mapa de bits usando una matriz bidimensional de valores de color, que es mucho, mucho, mucho más rápido. Y cuando termine con la imagen, la convierto en un mapa de bits GDI. También tengo una función que crea una matriz de pixmap a partir de un mapa de bits GDI.
he modificado el código anterior para dibujar en una matriz en lugar de un mapa de bits GDI, y el resultado es prometedor:
- Tiempo para rendir 100 líneas
- GDI Bitmap: 2,86 s
- Pixel matriz: 0,01 s
Si dejamos que
type
TPixmap = array of packed array of RGBQUAD;
y definir
procedure TForm3.DrawAntialisedLineOnPixmap(var Pixmap: TPixmap; const AX1, AY1, AX2, AY2: real; const LineColor: TColor);
var
swapped: boolean;
procedure plot(const x, y, c: real);
var
resclr: TRGBQuad;
begin
if swapped then
begin
if (x < 0) or (y < 0) or (x >= ClientWidth) or (y >= ClientHeight) then
Exit;
resclr := Pixmap[round(y), round(x)]
end
else
begin
if (y < 0) or (x < 0) or (y >= ClientWidth) or (x >= ClientHeight) then
Exit;
resclr := Pixmap[round(x), round(y)];
end;
resclr.rgbRed := round(resclr.rgbRed * (1-c) + GetRValue(LineColor) * c);
resclr.rgbGreen := round(resclr.rgbGreen * (1-c) + GetGValue(LineColor) * c);
resclr.rgbBlue := round(resclr.rgbBlue * (1-c) + GetBValue(LineColor) * c);
if swapped then
Pixmap[round(y), round(x)] := resclr
else
Pixmap[round(x), round(y)] := resclr;
end;
function rfrac(const x: real): real; inline;
begin
rfrac := 1 - frac(x);
end;
procedure swap(var a, b: real);
var
tmp: real;
begin
tmp := a;
a := b;
b := tmp;
end;
var
x1, x2, y1, y2, dx, dy, gradient, xend, yend, xgap, xpxl1, ypxl1,
xpxl2, ypxl2, intery: real;
x: integer;
begin
x1 := AX1;
x2 := AX2;
y1 := AY1;
y2 := AY2;
dx := x2 - x1;
dy := y2 - y1;
swapped := abs(dx) < abs(dy);
if swapped then
begin
swap(x1, y1);
swap(x2, y2);
swap(dx, dy);
end;
if x2 < x1 then
begin
swap(x1, x2);
swap(y1, y2);
end;
gradient := dy/dx;
xend := round(x1);
yend := y1 + gradient * (xend - x1);
xgap := rfrac(x1 + 0.5);
xpxl1 := xend;
ypxl1 := floor(yend);
plot(xpxl1, ypxl1, rfrac(yend) * xgap);
plot(xpxl1, ypxl1 + 1, frac(yend) * xgap);
intery := yend + gradient;
xend := round(x2);
yend := y2 + gradient * (xend - x2);
xgap := frac(x2 + 0.5);
xpxl2 := xend;
ypxl2 := floor(yend);
plot(xpxl2, ypxl2, rfrac(yend) * xgap);
plot(xpxl2, ypxl2 + 1, frac(yend) * xgap);
for x := round(xpxl1) + 1 to round(xpxl2) - 1 do
begin
plot(x, floor(intery), rfrac(intery));
plot(x, floor(intery) + 1, frac(intery));
intery := intery + gradient;
end;
end;
y la conversión funciones
var
pixmap: TPixmap;
procedure TForm3.CanvasToPixmap;
var
y: Integer;
Bitmap: TBitmap;
begin
Bitmap := TBitmap.Create;
try
Bitmap.SetSize(ClientWidth, ClientHeight);
Bitmap.PixelFormat := pf32bit;
BitBlt(Bitmap.Canvas.Handle, 0, 0, ClientWidth, ClientHeight, Canvas.Handle, 0, 0, SRCCOPY);
SetLength(pixmap, ClientHeight, ClientWidth);
for y := 0 to ClientHeight - 1 do
CopyMemory(@(pixmap[y][0]), Bitmap.ScanLine[y], ClientWidth * sizeof(RGBQUAD));
finally
Bitmap.Free;
end;
end;
procedure TForm3.PixmapToCanvas;
var
y: Integer;
Bitmap: TBitmap;
begin
Bitmap := TBitmap.Create;
try
Bitmap.PixelFormat := pf32bit;
Bitmap.SetSize(ClientWidth, ClientHeight);
for y := 0 to Bitmap.Height - 1 do
CopyMemory(Bitmap.ScanLine[y], @(Pixmap[y][0]), ClientWidth * sizeof(RGBQUAD));
Canvas.Draw(0, 0, Bitmap);
finally
Bitmap.Free;
end;
end;
entonces podemos escribir
procedure TForm3.FormPaint(Sender: TObject);
begin
// Get the canvas as a bitmap, and convert this to a pixmap
CanvasToPixmap;
// Draw on this pixmap (very fast!)
for i := 0 to 99 do
DrawAntialisedLineOnPixmap(pixmap, Random(ClientWidth), Random(ClientHeight), Random(ClientWidth), Random(ClientHeight), clRed);
// Convert the pixmap to a bitmap, and draw on the canvas
PixmapToCanvas;
end;
que rinda 100 líneas anti-alias en el formulario, en menos de una centésima de un segundo
Parece que hay un pequeño error en el código, probablemente, en la función Canvas -> Pixmap. Pero ahora mismo estoy demasiado cansado para depurarme (llegué a casa del trabajo).
El interés de nuestra biblioteca SynGdiPlus es que puede tener su código de dibujo escrito en métodos simples de VCL TCanvas. Dibuje en un TMetaFileCanvas, luego juegue en un mapa de bits usando nuestra biblioteca. Es muy rápido y funciona para mucho más que dibujar líneas. Y la huella añadida al ejecutable de su aplicación es insignificante. Le invitamos a que publique comentarios o preguntas sobre nuestra biblioteca en nuestro sitio web. Y no solo es demostrativo/educativo: se usa en aplicaciones reales, para una gran representación, con mucho código VCL TCanvas (otros programadores pensaron que era un dibujo de puntos :) –
GDI + se ajusta a mis requisitos (D7). – Ampere
SynGdiPlus funcionará con D7. Vea nuestro foro: http://synopse.info/forum/viewtopic.php?pid=498#p498 –