Tracé de ligne anti-aliasé 1/2
Par David CHARDONNET le 2 Mars 2010
Introduction
Voici le premier article de mon blog que je dédie aux diverses techniques de programmation. Nous allons utiliser le langage Pascal de Delphi pour illustrer notre propos, mais comme il est autant question d’algorithmique ici que de code, cette application peut se retranscrire en tout autre langage.
Le problème à résoudre
Nous allons nous intéresser ici à un problème que je rencontre dans le rafraîchissement d’un ancien logiciel freeware que j’avais développé en Delphi il y a quelques années: Brazil Fractal Builder. Ce logiciel permet de construire des objets fractals de type IFS. En clair ça vous permet de concevoir des dessins qui respectent la géométrie fractale, avec un assez bon niveau de contrôle sur ce que vous faites. C’est un peu comme du Lego à l’utilisation, on manipule des carrés qui sont des briques de base de la construction.
Voilà quelques exemples d’objets fractals IFS, parce qu’un bon dessin ça en dit toujours plus long qu’un terme en jargon incompréhensible:
Et il se trouve que pour dessiner ces trois images, on conçoit des cartes de transformations avec le concepteur, et les trois cartes de transformations correspondant respectivement aux images précédentes ressemblent à ceci:
Et c’est au niveau des cartes de transformations que je souhaite améliorer l’affichage. En effet lorsqu’on agrandit les cartes de transformation, la routine de tracé de lignes que j’utilise est basique et fait du crénelage (aussi appelé aliasing en anglais), ce qui nuit à l’esthétique du dessin. Voyez vous-même (cliquez sur l’image pour voir la brisure des lignes):
Début de solution
J’ai donc décidé d’implémenter une routine de tracé de ligne anti-aliasé dans ma bibliothèque graphique. Je ne veux pas réinventer la roue, donc, en faisant quelques recherches sur le web, j’ai trouvé quelques algorithmes, mais c’est plus particulièrement l’algorithme de tracé de segment de Xiaolin Wu, qui dispose d’un article sur Wikipédia avec du pseudo-code qui m’intéresse. Il a l’air d’être rapide, selon les informations que je dispose, et le clavier commence à me démanger rien que d’y penser.
Pseudo-code:
-
function plot(x, y, c) is
-
plot the pixel at (x, y) with brightness c (where 0 ≤ c ≤ 1)
-
-
function ipart(x) is
-
return integer part of x
-
-
function round(x) is
-
return ipart(x + 0.5)
-
-
function fpart(x) is
-
return fractional part of x
-
-
function rfpart(x) is
-
return 1 – fpart(x)
-
-
function drawLine(x1,y1,x2,y2) is
-
dx = x2 – x1
-
dy = y2 – y1
-
if abs(dx) < abs(dy) then
-
swap x1, y1
-
swap x2, y2
-
end if
-
if x2 < x1
-
swap x1, x2
-
swap y1, y2
-
end if
-
gradient = dy / dx
-
// handle first endpoint
-
xend = round(x1)
-
yend = y1 + gradient * (xend – x1)
-
xgap = rfpart(x1 + 0.5)
-
xpxl1 = xend // this will be used in the main loop
-
ypxl1 = ipart(yend)
-
plot(xpxl1, ypxl1, rfpart(yend) * xgap)
-
plot(xpxl1, ypxl1 + 1, fpart(yend) * xgap)
-
intery = yend + gradient // first y-intersection for the main loop
-
// handle second endpoint
-
xend = round (x2)
-
yend = y2 + gradient * (xend – x2)
-
xgap = fpart(x2 + 0.5)
-
xpxl2 = xend // this will be used in the main loop
-
ypxl2 = ipart (yend)
-
plot (xpxl2, ypxl2, rfpart (yend) * xgap)
-
plot (xpxl2, ypxl2 + 1, fpart (yend) * xgap)
-
// main loop
-
for x from xpxl1 + 1 to xpxl2 – 1 do
-
plot (x, ipart (intery), rfpart (intery))
-
plot (x, ipart (intery) + 1, fpart (intery))
-
intery = intery + gradient
-
end function
On trouve aussi l’information suivante sur Wikipédia:
Ci-dessous, le pseudo-code de l’algorithme dans le cas où la ligne est presque horizontale (Δx > Δy). Pour l’étendre à toutes les lignes, inversez les coordonnées x et y quand Δx < Δy (comme pour l’algorithme de Bresenham). Cette implémentation n’est valide que pour x ≥ 0 et y ≥ 0.
Au boulot!
Cet algorithme nécessite quelques adaptations pour pouvoir répondre à tous les cas de figure d’un tracé de ligne comme celui que je souhaite utiliser. En effet, il s’agit d’un algorithme élémentaire auquel il manque
- le cas où Δx < Δy ( le cas presque vertical)
- la couleur et le respect du fond
- la gestion du fenêtrage (clipping)
- la transparence
Nous détaillerons ces points un peu plus loin….
Première traduction brute du code en Pascal:
-
//****************************************************************************
-
//
-
//
-
//
-
//****************************************************************************
-
function wu_ipart(x:double):integer;
-
begin
-
result := floor(x);
-
end;
-
-
//****************************************************************************
-
//
-
//
-
//
-
//****************************************************************************
-
function wu_fpart(x:double):double;
-
begin
-
result := frac(x);
-
end;
-
-
//****************************************************************************
-
//
-
//
-
//
-
//****************************************************************************
-
function wu_rfpart(x:double):double;
-
begin
-
result := 1 – wu_fpart(x);
-
end;
-
-
//****************************************************************************
-
//
-
//
-
//
-
//****************************************************************************
-
procedure wu_swap(var a:integer;var b:integer);
-
var temp:integer;
-
begin
-
temp:=a;
-
a:=b;
-
b:=temp;
-
end;
-
-
//****************************************************************************
-
//
-
//
-
//
-
//****************************************************************************
-
function wu_round(x:double):integer;
-
begin
-
result := floor(x + 0.5);
-
end;
-
-
//****************************************************************************
-
//
-
// Pour le test, on a utilisé du noir sur un fond blanc alors que dans la
-
// logique de l’algorithme le parametre c est la luminosité ce qui dénote
-
// une logique de blanc sur fond noir c’est pour ça qu’on utilise (1.0-c)
-
// et on multiplie ensuite par 255 pour obtenir une valeur octet [0;255]
-
//
-
//****************************************************************************
-
procedure CHiColor.wu_plot(x, y:integer; c:double);
-
var coul:byte;
-
begin
-
coul:=floor(255.0*(1.0-c));
-
putpixel(x,y,coul,coul,coul);
-
end;
-
-
//****************************************************************************
-
//
-
// Fonction qui permet de tracer les lignes "presque horizontales"
-
//
-
//****************************************************************************
-
procedure CHiColor.wu_drawLine(x1,y1,x2,y2:integer);
-
var dx:double;
-
dy:double;
-
gradient:double;
-
xend:double;
-
yend:double;
-
xgap:double;
-
xpxl1:integer;
-
ypxl1:integer;
-
xpxl2:integer;
-
ypxl2:integer;
-
intery:double;
-
x:integer;
-
begin
-
dx := x2 – x1;
-
dy := y2 – y1;
-
if (abs(dx) > abs(dy)) then // Test inversé car
-
begin //
-
// wu_swap(x1,y1); // ce code ne fonctionne pas
-
// wu_swap(x2,y2); // on conserve uniquement le code qui
-
//end; // fonctionne
-
if (x2 < x1) then
-
begin
-
wu_swap(x1,x2);
-
wu_swap(y1,y2);
-
end;
-
gradient := dy / dx;
-
-
// handle first endpoint
-
-
xend := wu_round(x1);
-
yend := y1 + gradient * (xend – x1);
-
xgap := wu_rfpart(x1 + 0.5);
-
xpxl1 := round(xend); // this will be used in the main loop
-
ypxl1 := wu_ipart(yend);
-
wu_plot(xpxl1, ypxl1, wu_rfpart(yend) * xgap);
-
wu_plot(xpxl1, ypxl1 + 1, wu_fpart(yend) * xgap);
-
intery := yend + gradient; // first y-intersection for the main loop
-
-
// handle second endpoint
-
-
xend := round (x2);
-
yend := y2 + gradient * (xend – x2);
-
xgap := wu_fpart(x2 + 0.5);
-
xpxl2 := round(xend); // this will be used in the main loop
-
ypxl2 := wu_ipart (yend);
-
wu_plot (xpxl2, ypxl2, wu_rfpart (yend) * xgap);
-
wu_plot (xpxl2, ypxl2 + 1, wu_fpart (yend) * xgap);
-
-
// main loop
-
for x := xpxl1 + 1 to xpxl2 – 1 do
-
begin
-
wu_plot (x, wu_ipart (intery), wu_rfpart (intery));
-
wu_plot (x, wu_ipart (intery) + 1, wu_fpart (intery));
-
intery := intery + gradient;
-
end;
-
end;
-
end;
Dans ce code on retrouve strictement les noms des fonctions et les noms de variables du code d’origine. Seulement, après test, il s’avère qu’une partie du code n’est pas fonctionnelle et elle a été retirée. tous les noms de fonction ont été préfixés par “wu_” afin de les différencier des fonctions déjà existantes dans Delphi (par exemple round() existe déjà dans Delphi, il fallait les différencier).
Le nom de la classe de ma librairie graphique est CHiColor, et on utilise la méthode putpixel qui affiche un point de couleur.
Premier test de la fonction
-
procedure TForm1.Button1Click(Sender: TObject);
-
var i:integer;
-
begin
-
HiColor.nettoyer_et_afficher(255,255,255);
-
-
// L’ancienne fonction de dessin de ligne
-
HiColor.Line(10,10,100,30,0,0,0);
-
-
// Presque horizontal
-
HiColor.wu_drawLine(10,15,100,35);
-
-
HiColor.afficher;
-
end;
A l’exécution de ce test on obtient l’affichage suivant (après zoom):
On peut constater que l’effet d’escalier de la ligne du dessus n’est plus présent dans la ligne du dessous. L’algorithme fonctionne donc. C’est encourageant, mais on est limité aux lignes “presques horizontales”.
Presque vertical
C’est le moment de se servir de la recommandation de wikipédia:
Pour l’étendre à toutes les lignes, inversez les coordonnées x et y quand Δx < Δy (comme pour l’algorithme de Bresenham). Cette implémentation n’est valide que pour x ≥ 0 et y ≥ 0.
On rectifie donc le code de la procédure de cette façon:
-
//****************************************************************************
-
//
-
//
-
//
-
//****************************************************************************
-
function wu_rfpart(x:double):double;
-
begin
-
result := 1 – frac(x);
-
end;
-
-
//****************************************************************************
-
//
-
//
-
//
-
//****************************************************************************
-
procedure wu_swap(var a:integer;var b:integer);
-
var temp:integer;
-
begin
-
temp:=a;
-
a:=b;
-
b:=temp;
-
end;
-
-
//****************************************************************************
-
//
-
//
-
//
-
//****************************************************************************
-
function wu_round(x:double):integer;
-
begin
-
result := floor(x + 0.5);
-
end;
-
-
//****************************************************************************
-
//
-
// Pour le test, on a utilisé du noir sur un fond blanc alors que dans la
-
// logique de l’algorithme le parametre c est la luminosité ce qui dénote
-
// une logique de blanc sur fond noir c’est pour ça qu’on utilise (1.0-c)
-
// et on multiplie ensuite par 255 pour obtenir une valeur octet [0;255]
-
//
-
//****************************************************************************
-
procedure CHiColor.wu_plot(x, y:integer; c:double);
-
var coul:byte;
-
begin
-
coul:=floor(255.0*(1.0-c));
-
putpixel(x,y,coul,coul,coul);
-
end;
-
-
//****************************************************************************
-
//
-
// Fonction qui permet de tracer les lignes noires anti-aliasées sur un fond blanc
-
//
-
//****************************************************************************
-
procedure CHiColor.wu_drawLine(x1,y1,x2,y2:integer);
-
var dx:double;
-
dy:double;
-
gradient:double;
-
xend:double;
-
yend:double;
-
xgap:double;
-
ygap:double;
-
xpxl1:integer;
-
ypxl1:integer;
-
xpxl2:integer;
-
ypxl2:integer;
-
interx:double;
-
intery:double;
-
x:integer;
-
y:integer;
-
begin
-
dx := x2 – x1;
-
dy := y2 – y1;
-
if (abs(dx) > abs(dy)) then
-
begin
-
//——————–
-
// Presque horizontal
-
//——————–
-
if (x2 < x1) then
-
begin
-
wu_swap(x1,x2);
-
wu_swap(y1,y2);
-
end;
-
gradient := dy / dx;
-
-
// handle first endpoint
-
-
xend := wu_round(x1);
-
yend := y1 + gradient * (xend – x1);
-
xgap := wu_rfpart(x1 + 0.5);
-
xpxl1 := round(xend); // this will be used in the main loop
-
ypxl1 := floor(yend);
-
wu_plot(xpxl1, ypxl1, wu_rfpart(yend) * xgap);
-
wu_plot(xpxl1, ypxl1 + 1, frac(yend) * xgap);
-
intery := yend + gradient; // first y-intersection for the main loop
-
-
// handle second endpoint
-
-
xend := round (x2);
-
yend := y2 + gradient * (xend – x2);
-
xgap := frac(x2 + 0.5);
-
xpxl2 := round(xend); // this will be used in the main loop
-
ypxl2 := floor(yend);
-
wu_plot (xpxl2, ypxl2 , wu_rfpart (yend) * xgap);
-
wu_plot (xpxl2, ypxl2 + 1, frac (yend) * xgap);
-
-
// main loop
-
for x := xpxl1 + 1 to xpxl2 – 1 do
-
begin
-
wu_plot (x, floor (intery) , wu_rfpart (intery));
-
wu_plot (x, floor (intery) + 1, frac (intery));
-
intery := intery + gradient;
-
end;
-
end
-
else
-
begin
-
//——————
-
// Presque vertical
-
//——————
-
if ( y2 < y1 ) then
-
begin
-
wu_swap(x1, x2);
-
wu_swap(y1, y2);
-
end;
-
-
gradient := dx / dy;
-
-
// handle first endpoint
-
-
yend := wu_round(y1);
-
xend := x1 + gradient*(yend – y1);
-
ygap := wu_rfpart(y1 + 0.5);
-
ypxl1 := round(yend); // this will be used in the main loop
-
xpxl1 := floor(xend);
-
wu_plot(xpxl1, ypxl1 , wu_rfpart (xend) * ygap);
-
wu_plot(xpxl1, ypxl1 + 1, frac (xend) * ygap);
-
interx := xend + gradient; // first x-intersection for the main loop
-
-
// handle second endpoint
-
-
yend := wu_round(y2);
-
xend := x2 + gradient*(yend – y2);
-
ygap := frac(y2 + 0.5);
-
ypxl2 := round(yend); // this will be used in the main loop
-
xpxl2 := floor(xend);
-
wu_plot(xpxl2, ypxl2 , wu_rfpart (xend) * ygap);
-
wu_plot(xpxl2, ypxl2 + 1, frac (xend) * ygap);
-
-
// main loop
-
for y := ypxl1 + 1 to ypxl2 – 1 do
-
begin
-
wu_plot(floor(interx) , y, wu_rfpart (interx));
-
wu_plot(floor(interx) + 1, y, frac (interx));
-
interx := interx + gradient;
-
end;
-
end;
-
end;
On a rajouté un bloc de code pour gérer les lignes presque verticales.
Vous noterez par ailleurs que j’ai éliminé les fonctions wu_fpart et wu_ipart pour les remplacer par les fonctions frac et floor dans le code.
Après test, on obtient ce résultat:
La couleur et le respect du fond
Vous admettrez qu’une touche de couleur nous ferait le plus grand bien. Mais il faut aussi penser qu’avec ce système de dessin, le fond de l’image n’est pas respecté. Voyez ce test sur un fond rouge:
Donc pour être tout-terrain, notre fonction doit prendre en compte la ou les couleurs du fond sur lequel elle est utilisée. On va donc porter l’accent sur la fonction wu_plot qui est la clé du problème.
-
//****************************************************************************
-
//
-
//
-
//
-
//
-
//
-
//
-
//****************************************************************************
-
procedure CHiColor.wu_plot(x, y:integer; c:double;r,v,b:byte);
-
var resultat_r,resultat_v,resultat_b:byte;
-
fond_r,fond_v,fond_b:byte;
-
begin
-
// On lit la couleur du pixel sur lequel on va dessiner
-
getpixel(x,y,fond_r,fond_v,fond_b);
-
-
// On calcule le mélange des couleurs du fond et de la ligne
-
resultat_r:=floor(fond_r*(1.0-c)+c*r);
-
resultat_v:=floor(fond_v*(1.0-c)+c*v);
-
resultat_b:=floor(fond_b*(1.0-c)+c*b);
-
-
putpixel(x,y,resultat_r,resultat_v,resultat_b);
-
end;
-
-
//****************************************************************************
-
//
-
// Fonction
-
//
-
//——————————————————————————
-
procedure CHiColor.wu_drawLineX(x1,y1,x2,y2:integer;r,v,b:byte);
-
var dx:double;
-
dy:double;
-
gradient:double;
-
xend:double;
-
yend:double;
-
xgap:double;
-
ygap:double;
-
xpxl1:integer;
-
ypxl1:integer;
-
xpxl2:integer;
-
ypxl2:integer;
-
interx:double;
-
intery:double;
-
x:integer;
-
y:integer;
-
begin
-
dx := x2 – x1;
-
dy := y2 – y1;
-
if (abs(dx) > abs(dy)) then
-
begin
-
//——————–
-
// Presque horizontal
-
//——————–
-
if (x2 < x1) then
-
begin
-
wu_swap(x1,x2);
-
wu_swap(y1,y2);
-
end;
-
gradient := dy / dx;
-
-
// handle first endpoint
-
-
xend := wu_round(x1);
-
yend := y1 + gradient * (xend – x1);
-
xgap := wu_rfpart(x1 + 0.5);
-
xpxl1 := round(xend); // this will be used in the main loop
-
ypxl1 := floor(yend);
-
wu_plot(xpxl1, ypxl1, wu_rfpart(yend) * xgap,r,v,b);
-
wu_plot(xpxl1, ypxl1 + 1, frac(yend) * xgap,r,v,b);
-
intery := yend + gradient; // first y-intersection for the main loop
-
-
// handle second endpoint
-
-
xend := round (x2);
-
yend := y2 + gradient * (xend – x2);
-
xgap := frac(x2 + 0.5);
-
xpxl2 := round(xend); // this will be used in the main loop
-
ypxl2 := floor(yend);
-
wu_plot (xpxl2, ypxl2 , wu_rfpart (yend) * xgap,r,v,b);
-
wu_plot (xpxl2, ypxl2 + 1, frac (yend) * xgap,r,v,b);
-
-
// main loop
-
for x := xpxl1 + 1 to xpxl2 – 1 do
-
begin
-
wu_plot (x, floor (intery) , wu_rfpart (intery),r,v,b);
-
wu_plot (x, floor (intery) + 1, frac (intery),r,v,b);
-
intery := intery + gradient;
-
end;
-
end
-
else
-
begin
-
//——————
-
// Presque vertical
-
//——————
-
if ( y2 < y1 ) then
-
begin
-
wu_swap(x1, x2);
-
wu_swap(y1, y2);
-
end;
-
-
gradient := dx / dy;
-
-
// handle first endpoint
-
-
yend := wu_round(y1);
-
xend := x1 + gradient*(yend – y1);
-
ygap := wu_rfpart(y1 + 0.5);
-
ypxl1 := round(yend); // this will be used in the main loop
-
xpxl1 := floor(xend);
-
wu_plot(xpxl1, ypxl1 , wu_rfpart (xend) * ygap,r,v,b);
-
wu_plot(xpxl1, ypxl1 + 1, frac (xend) * ygap,r,v,b);
-
interx := xend + gradient; // first x-intersection for the main loop
-
-
// handle second endpoint
-
-
yend := wu_round(y2);
-
xend := x2 + gradient*(yend – y2);
-
ygap := frac(y2 + 0.5);
-
ypxl2 := round(yend); // this will be used in the main loop
-
xpxl2 := floor(xend);
-
wu_plot(xpxl2, ypxl2 , wu_rfpart (xend) * ygap,r,v,b);
-
wu_plot(xpxl2, ypxl2 + 1, frac (xend) * ygap,r,v,b);
-
-
// main loop
-
for y := ypxl1 + 1 to ypxl2 – 1 do
-
begin
-
wu_plot(floor(interx) , y, wu_rfpart (interx),r,v,b);
-
wu_plot(floor(interx) + 1, y, frac (interx),r,v,b);
-
interx := interx + gradient;
-
end;
-
end;
-
end;
Vous pouvez constater qu’on a rajouté dans wu_plot:
- la lecture de la couleur du fond sur lequel on s’apprête à tracer
- en paramètre la couleur rvb souhaitée de la ligne
- une fonction de “mélange” des couleurs
Le paramètre c jouant sur l’intensité de la couleur de la ligne sur le pixel concerné:
- c=1 -> la ligne efface complètement le pixel dessiné
- c=0 -> le fond n’est pas changé
- c=0.5 -> le fond et la ligne occupent la même place dans le mélange
- etc..
Un petit test, en variant la couleur de fond nous prouve le bon fonctionnement de tout ça:
Voir la suite – Tracé de ligne anti-aliasé 2/2
Tracé de ligne anti-aliasé 2/2
Par David CHARDONNET le 2 Mars 2010
Voir le début de l’article – Tracé de ligne anti-aliasé 1/2
Est-on satisfait de notre code?
Eh bien non, pas tout à fait si on y regarde de plus près. Il y a deux cas aux limites qui n’ont pas été vérifiés: lorsque les lignes sont horizontales ou verticales. Et dans ce cas, on commet le pire: la division par zéro!! Et là, c’est le drame, votre programme plante, et l’utilisateur a un message qu’il ne comprend pas, et déteste encore plus son ordinateur.
Cette petite négligence nous donne l’opportunité d’améliorer sensiblement les performances de notre algorithme pour ces deux cas aux limites.
-
//********************************************************************
-
//
-
// Fonction de tracé de pixel avec transparence (alpha-blending)
-
//
-
//********************************************************************
-
procedure CHiColor.putpixel_alpha(x, y:integer; alpha:double;r,v,b:byte);
-
var resultat_r,resultat_v,resultat_b:byte;
-
fond_r,fond_v,fond_b:byte;
-
begin
-
// On lit la couleur du pixel sur lequel on va dessiner
-
getpixel(x,y,fond_r,fond_v,fond_b);
-
-
// On calcule le mélange des couleurs du fond et de la ligne
-
resultat_r:=floor(fond_r*(1.0-alpha)+alpha*r);
-
resultat_v:=floor(fond_v*(1.0-alpha)+alpha*v);
-
resultat_b:=floor(fond_b*(1.0-alpha)+alpha*b);
-
-
putpixel(x,y,resultat_r,resultat_v,resultat_b);
-
end;
-
-
//********************************************************************
-
//
-
// Fonction de tracé de ligne anti-aliasee
-
// selon l’algorithme de Xiaolin Wu
-
//
-
//********************************************************************
-
procedure CHiColor.wu_drawLine(x1,y1,x2,y2:integer;r,v,b:byte);
-
var dx:double;
-
dy:double;
-
gradient:double;
-
xend:double;
-
yend:double;
-
xgap:double;
-
ygap:double;
-
xpxl1:integer;
-
ypxl1:integer;
-
xpxl2:integer;
-
ypxl2:integer;
-
interx:double;
-
intery:double;
-
x:integer;
-
y:integer;
-
begin
-
dx := x2 – x1;
-
dy := y2 – y1;
-
if (dx=0) then
-
begin
-
//—————–
-
// Ligne verticale
-
//—————–
-
for y:=min(y1,y2) to max(y1,y2) do
-
begin
-
putpixel(x1,y,r,v,b);
-
end;
-
end
-
else if (dy=0) then
-
begin
-
//——————-
-
// Ligne horizontale
-
//——————-
-
for x:=min(x1,x2) to max(x1,x2) do
-
begin
-
putpixel(x,y1,r,v,b);
-
end;
-
end
-
else
-
begin
-
if (abs(dx) > abs(dy)) then
-
begin
-
//——————–
-
// Presque horizontal
-
//——————–
-
if (x2 < x1) then
-
begin
-
wu_swap(x1,x2);
-
wu_swap(y1,y2);
-
end;
-
gradient := dy / dx;
-
-
// premiere extremite
-
-
xend := wu_round(x1);
-
yend := y1 + gradient * (xend – x1);
-
xgap := wu_rfpart(x1 + 0.5);
-
xpxl1 := round(xend); // ceci sera utilisé dans la boucle principale
-
ypxl1 := floor(yend);
-
putpixel_alpha(xpxl1, ypxl1, wu_rfpart(yend) * xgap,r,v,b);
-
putpixel_alpha(xpxl1, ypxl1 + 1, frac(yend) * xgap,r,v,b);
-
intery := yend + gradient; // premiere intersection-y pour la boucle principale
-
-
// seconde extremite
-
-
xend := round (x2);
-
yend := y2 + gradient * (xend – x2);
-
xgap := frac(x2 + 0.5);
-
xpxl2 := round(xend); // ceci sera utilisé dans la boucle principale
-
ypxl2 := floor(yend);
-
putpixel_alpha (xpxl2, ypxl2 , wu_rfpart (yend) * xgap,r,v,b);
-
putpixel_alpha (xpxl2, ypxl2 + 1, frac (yend) * xgap,r,v,b);
-
-
// boucle principale
-
for x := xpxl1 + 1 to xpxl2 – 1 do
-
begin
-
putpixel_alpha (x, floor (intery) , wu_rfpart (intery),r,v,b);
-
putpixel_alpha (x, floor (intery) + 1, frac (intery),r,v,b);
-
intery := intery + gradient;
-
end;
-
end
-
else
-
begin
-
//——————
-
// Presque vertical
-
//——————
-
if ( y2 < y1 ) then
-
begin
-
wu_swap(x1, x2);
-
wu_swap(y1, y2);
-
end;
-
-
gradient := dx / dy;
-
-
// premiere extremite
-
-
yend := wu_round(y1);
-
xend := x1 + gradient*(yend – y1);
-
ygap := wu_rfpart(y1 + 0.5);
-
ypxl1 := round(yend); // ceci sera utilisé dans la boucle principale
-
xpxl1 := floor(xend);
-
putpixel_alpha(xpxl1, ypxl1 , wu_rfpart (xend) * ygap,r,v,b);
-
putpixel_alpha(xpxl1, ypxl1 + 1, frac (xend) * ygap,r,v,b);
-
interx := xend + gradient; // premiere intersection-x pour la boucle principale
-
-
// seconde extremite
-
-
yend := wu_round(y2);
-
xend := x2 + gradient*(yend – y2);
-
ygap := frac(y2 + 0.5);
-
ypxl2 := round(yend); // ceci sera utilisé dans la boucle principale
-
xpxl2 := floor(xend);
-
putpixel_alpha(xpxl2, ypxl2 , wu_rfpart (xend) * ygap,r,v,b);
-
putpixel_alpha(xpxl2, ypxl2 + 1, frac (xend) * ygap,r,v,b);
-
-
// boucle principale
-
for y := ypxl1 + 1 to ypxl2 – 1 do
-
begin
-
putpixel_alpha(floor(interx) , y, wu_rfpart (interx),r,v,b);
-
putpixel_alpha(floor(interx) + 1, y, frac (interx),r,v,b);
-
interx := interx + gradient;
-
end;
-
end;
-
end;
-
end;
Donc vous constaterez que j’ai renommé la fonction wu_plot en putpixel_alpha, parce que dans un souci de clarté, il faut appeler les choses par leur nom. Et cette fonction nous fait bien un affichage de pixel en transparence donc en “alpha-blending”. Le paramètre c gérant la transparence, est renommé alpha. De cette façon, nous pourrons réutiliser cette fonction pour nos autres opérations transparentes.
Optimisons
Vous savez sans doute que pour bien optimiser, on s’attaque en premier aux opérations qui se répètent le plus pour réduire leur temps d’exécution. C’est ce qu’on va faire ici avec la fonction qu’on utilise le plus: putpixel_alpha. On suppose que notre fonction putpixel est déjà optimisée.
On s’aperçoit qu’il est possible d’accélérer putpixel_alpha sur les formules de mélange:
La formule fond_r*(1.0-alpha)+alpha*r utilise 2 multiplications une soustraction et une addition
Avec un peu d’algèbre de base on peut regrouper cette formule en alpha*(r-fond_r)+fond_r qui utilise une multiplication une addition et une soustraction
On a alors économisé une multiplication, qui est, au niveau de microprocesseur, une opération plus coûteuse que les additions et soustractions.
Tout ça pour une multiplication me direz-vous? Oui mais n’oublions pas que sur le tracé d’une ligne nécessitant le tracé de 300 pixels par exemple, on aura économisé 300 multiplications juste pour cette ligne. Et puis en cette époque de surpuissance des matériels, il faut quand même savoir économiser les ressources machine, c’est aussi ça l’écologie.
-
//********************************************************************
-
//
-
// Fonction de tracé de pixel avec transparence (alpha-blending)
-
//
-
//********************************************************************
-
procedure CHiColor.putpixel_alpha(x, y:integer; alpha:double;r,v,b:byte);
-
var resultat_r,resultat_v,resultat_b:byte;
-
fond_r,fond_v,fond_b:byte;
-
begin
-
// On lit la couleur du pixel sur lequel on va dessiner
-
getpixel(x,y,fond_r,fond_v,fond_b);
-
-
// On calcule le mélange des couleurs du fond et de la ligne
-
resultat_r:=floor(alpha*(r-fond_r)+fond_r);
-
resultat_v:=floor(alpha*(v-fond_v)+fond_v);
-
resultat_b:=floor(alpha*(b-fond_b)+fond_b);
-
-
putpixel(x,y,resultat_r,resultat_v,resultat_b);
-
end;
Votre chef de projet veut encore optimiser??
Ah, les chefs, ils en veulent toujours trop, et ils ne vous donnent jamais assez de temps pour tout faire. Bon alors ce que je propose c’est qu’on rogne un peu sur la lisibilité du code et qu’on réintègre les petites fonctions à l’intérieur de la grosse. Ca épargnera quelques appels de fonctions ce qui aura pour effet de gagner quelques cycles machine ainsi que de réduire l’utilisation de la pile. C’est peu, mais une librairie, c’est toujours mieux lorsqu’elle est légère.
Les fonctions wu_rfpart wu_round, et wu_swap vont être éliminées. (pour les programmeurs en C ou C++, la fonction swap existe en natif, donc oubliez celle-là).
-
//********************************************************************
-
//
-
// Fonction de tracé de ligne anti-aliasee
-
// selon l’algorithme de Xiaolin Wu
-
//
-
//********************************************************************
-
procedure CHiColor.wu_drawLine(x1,y1,x2,y2:integer;r,v,b:byte);
-
var dx:double;
-
dy:double;
-
gradient:double;
-
xend:double;
-
yend:double;
-
xgap:double;
-
ygap:double;
-
xpxl1:integer;
-
ypxl1:integer;
-
xpxl2:integer;
-
ypxl2:integer;
-
interx:double;
-
intery:double;
-
x:integer;
-
y:integer;
-
temp:integer;
-
begin
-
dx := x2 – x1;
-
dy := y2 – y1;
-
if (dx=0) then
-
begin
-
//—————–
-
// Ligne verticale
-
//—————–
-
for y:=min(y1,y2) to max(y1,y2) do
-
begin
-
putpixel_notest(x1,y,r,v,b);
-
end;
-
end
-
else if (dy=0) then
-
begin
-
//——————-
-
// Ligne horizontale
-
//——————-
-
for x:=min(x1,x2) to max(x1,x2) do
-
begin
-
putpixel_notest(x,y1,r,v,b);
-
end;
-
end
-
else
-
begin
-
if (abs(dx) > abs(dy)) then
-
begin
-
//——————–
-
// Presque horizontal
-
//——————–
-
if (x2 < x1) then
-
begin
-
// Echange de x1 et x2
-
temp:=x1;
-
x1:=x2;
-
x2:=temp;
-
//————-
-
// Echange de y1 et y2
-
temp:=y1;
-
y1:=y2;
-
y2:=temp;
-
end;
-
gradient := dy / dx;
-
-
// premiere extremite
-
-
xend := floor(x1+0.5);
-
yend := y1 + gradient * (xend – x1);
-
xgap := (1.0-frac(x1 + 0.5));
-
xpxl1 := round(xend); // ceci sera utilisé dans la boucle principale
-
ypxl1 := floor(yend);
-
putpixel_alpha(xpxl1, ypxl1, (1.0-frac(yend)) * xgap,r,v,b);
-
putpixel_alpha(xpxl1, ypxl1 + 1, frac(yend) * xgap,r,v,b);
-
intery := yend + gradient; // premiere intersection-y pour la boucle principale
-
-
// seconde extremite
-
-
xend := round (x2);
-
yend := y2 + gradient * (xend – x2);
-
xgap := frac(x2 + 0.5);
-
xpxl2 := round(xend); // ceci sera utilisé dans la boucle principale
-
ypxl2 := floor(yend);
-
putpixel_alpha (xpxl2, ypxl2 , (1.0-frac(yend)) * xgap,r,v,b);
-
putpixel_alpha (xpxl2, ypxl2 + 1, frac(yend) * xgap,r,v,b);
-
-
// boucle principale
-
for x := xpxl1 + 1 to xpxl2 – 1 do
-
begin
-
putpixel_alpha (x, floor (intery) , 1.0-frac(intery),r,v,b);
-
putpixel_alpha (x, floor (intery) + 1, frac(intery),r,v,b);
-
intery := intery + gradient;
-
end;
-
end
-
else
-
begin
-
//——————
-
// Presque vertical
-
//——————
-
if ( y2 < y1 ) then
-
begin
-
// Echange de x1 et x2
-
temp:=x1;
-
x1:=x2;
-
x2:=temp;
-
//————-
-
// Echange de y1 et y2
-
temp:=y1;
-
y1:=y2;
-
y2:=temp;
-
end;
-
-
gradient := dx / dy;
-
-
// premiere extremite
-
-
yend := floor(y1+0.5);
-
xend := x1 + gradient*(yend – y1);
-
ygap := 1.0-frac(y1 + 0.5);
-
ypxl1 := round(yend); // ceci sera utilisé dans la boucle principale
-
xpxl1 := floor(xend);
-
putpixel_alpha(xpxl1, ypxl1 , (1.0-frac (xend)) * ygap,r,v,b);
-
putpixel_alpha(xpxl1, ypxl1 + 1, frac (xend) * ygap,r,v,b);
-
interx := xend + gradient; // premiere intersection-x pour la boucle principale
-
-
// seconde extremite
-
-
yend := floor(y2+0.5);
-
xend := x2 + gradient*(yend – y2);
-
ygap := frac(y2 + 0.5);
-
ypxl2 := round(yend); // ceci sera utilisé dans la boucle principale
-
xpxl2 := floor(xend);
-
putpixel_alpha(xpxl2, ypxl2 , (1.0-frac (xend)) * ygap,r,v,b);
-
putpixel_alpha(xpxl2, ypxl2 + 1, frac (xend) * ygap,r,v,b);
-
-
// boucle principale
-
for y := ypxl1 + 1 to ypxl2 – 1 do
-
begin
-
putpixel_alpha(floor(interx) , y, (1.0-frac (interx)),r,v,b);
-
putpixel_alpha(floor(interx) + 1, y, frac (interx),r,v,b);
-
interx := interx + gradient;
-
end;
-
end;
-
end;
-
end;
On a un peu perdu en lisibilité du code, mais on ne peut pas tout avoir. Le chef de projet devrait être content à présent. On arrêtera là les optimisations, parce qu’on peut aller très loin.
Le clipping par Cohen-Sutherland
On aimerait que ce soit fini, mais non, il nous reste encore le clipping ou fenêtrage, qui nous permettra de tracer une ligne sans faire planter lamentablement notre application. C’est souvent utile lorsque les extrémités sont calculées par un programme, et que les limites sont parfois en dehors de la zone de dessin et qu’on veut que le bout de ligne qui doit être affiché s’affiche quand même.. Elle fait appel à la procédure de tracé de ligne précédemment écrite et c’est donc cette nouvelle fonction qui devra être appelée par le programme utilisateur.
-
//**********************************************************************
-
//
-
// Algorithme de Cohen-Sutherland pour le clipping de lignes
-
//
-
//**********************************************************************
-
procedure CHiColor.lineclip(x0,y0,x1,y1:double;r,g,b:byte);
-
type edge = (GAUCHE,DROITE,BAS,HAUT);
-
outcode = set of edge;
-
var accept,done:boolean;
-
outcode0,outcode1,outcodeOut:outcode;
-
x,y:double;
-
dx,dy:double;
-
begin
-
x:=0.0;
-
y:=0.0;
-
-
accept:=false;
-
done:=false;
-
//————————————————-
-
outcode0:=[];
-
if (y0 > m_ymax_phys) then outcode0:=[BAS]
-
else if (y0 < m_ymin_phys) then outcode0:=[HAUT];
-
if (x0 > m_xmax_phys) then outcode0:=outcode0+[DROITE]
-
else if (x0 < m_xmin_phys) then outcode0:=outcode0+[GAUCHE];
-
//————————————————-
-
outcode1:=[];
-
if (y1 > m_ymax_phys) then outcode1:=[BAS]
-
else if (y1 < m_ymin_phys) then outcode1:=[HAUT];
-
if (x1 > m_xmax_phys) then outcode1:=outcode1+[DROITE]
-
else if (x1 < m_xmin_phys) then outcode1:=outcode1+[GAUCHE];
-
//————————————————-
-
repeat
-
// Intérieur de la fenêtre – > On trace directement
-
if ((outcode0=[]) and (outcode1=[])) then
-
begin
-
accept:=true;
-
done:=true;
-
end
-
else
-
// Même demi-plan extérieur – > rien a dessiner
-
if (outcode0*outcode1) <> [] then
-
begin
-
done:=true;
-
end
-
-
else
-
begin
-
//————————————
-
if (outcode0 <> []) then
-
outcodeOut:=outcode0
-
else
-
outcodeOut:=outcode1;
-
//————————————
-
dx:=x1-x0;
-
dy:=y1-y0;
-
//————————————
-
if BAS in outcodeOut then
-
begin
-
x:=x0+dx*(m_ymax_phys-y0)/dy;
-
y:=m_ymax_phys;
-
end
-
//————————————
-
else if HAUT in outcodeOut then
-
begin
-
x:=x0+dx*(m_ymin_phys-y0)/dy;
-
y:=m_ymin_phys;
-
end;
-
//————————————
-
if DROITE in outcodeOut then
-
begin
-
x:=m_xmax_phys;
-
y:=y0+dy*(m_xmax_phys-x0)/dx;
-
end
-
//————————————
-
else if GAUCHE in outcodeOut then
-
begin
-
x:=m_ymin_phys;
-
y:=y0+dy*(m_xmin_phys-x0)/dx;
-
end;
-
//————————————
-
if (outcodeOut = outcode0) then
-
begin
-
x0:=x;
-
y0:=y;
-
-
outcode0:=[];
-
if (y0 > m_ymax_phys) then outcode0:=[BAS]
-
else if (y0 < m_ymin_phys) then outcode0:=[HAUT];
-
if (x0 > m_xmax_phys) then outcode0:=outcode0+[DROITE]
-
else if (x0 < m_xmin_phys) then outcode0:=outcode0+[GAUCHE];
-
end
-
else
-
begin
-
x1:=x;
-
y1:=y;
-
-
outcode1:=[];
-
if (y1 > m_ymax_phys) then outcode1:=[BAS]
-
else if (y1 < m_ymin_phys) then outcode1:=[HAUT];
-
if (x1 > m_xmax_phys) then outcode1:=outcode1+[DROITE]
-
else if (x1 < m_xmin_phys) then outcode1:=outcode1+[GAUCHE];
-
-
end;
-
//————————————
-
-
end;
-
until done;
-
-
if (accept) then
-
begin
-
wu_drawLine(trunc(x0),trunc(y0),trunc(x1),trunc(y1),r,g,b);
-
end;
-
end;
Voilà, je pense que nous avons fait le tour du problème, et j’espère que l’article vous a plu. N’hésitez pas à me faire part de vos commentaires, remarques, améliorations, je m’efforcerai d’y répondre dans un temps raisonnable.
David CHARDONNET, ClearAlgo











