Nous rappelons que, pour Up ! Graphical Engine, le repère de coordonnées est orienté de :
Une image est Bitmap est organisée en lignes de pixels modélisées par un tableau d'octets. Selon la palette, un pixel nécessite d'une fraction d'octet à plusieurs octets.
De ce fait, il est plus simple et efficace de tracer :
Ainsi, nous utiliserons deux fonctions :
De plus, nous joignons les lignes élémentaires de même ordonnée pour n'en tracer qu'une seule.
(P1.x, P1.y)
au point P2 de coordonnées (P2.x, P2.y)
. Nous supposons que :
P1.y==P2.y
. Elle est triviale à tracer.P1.x==P2.x
. Elle est triviale à tracer.P1.x<P2.x Et P1.y<P2.y
. Les autres cas de figure se déduisent par symétrie horizontale ou verticale.
Quitte à faire une translation de
(P1.x, P1.y)
, l'équation de la droite est a*X + b*Y = 0
.
Cela revient donc, après translation, à tracer la ligne du point de coordonnées (0, 0)
au point de coordonnées (P2.x - P1.x, P2.y - P1.y)
. Nous prenons :
a = P2.y - P1.y = DeltaY
.
b = -(P2.x - P1.x) = - DeltaX
.
Nous constatons que :
a
est positif.
b
est négatif.
DeltaX>DeltaY
.
Pour la dessiner, les pixels sont parcourus de l'abscisse P1.x
à P2.x
. L'ordonnée du premier pixel commence à P1.y
.
Pour un point (Pi.x, Pi.y)
tracé, compte tenu de l'alignement sur la grille des pixels, la question est de savoir si le prochain point à tracer est soit :
(Pi.x + 1, Pi.y)
- On reste sur la même ordonnée en se décalant à gauche.
(Pi.x + 1, Pi.y + 1)
- On descend en se décalant à gauche.
Pour cela, nous évaluons une erreur d'alignement donnée par la distance à la ligne calculée par application de l'équation de la droite :
ErreurY(Pi.x + 1, Pi.y) = a * (Pi.x + 1) + b * Pi.y = (a * Pi.x + b * Pi.y) + a = ErreurY(Pi.x, Pi.y) + a
.
ErreurY(Pi.x + 1, Pi.y + 1) = a * (Pi.x + 1) + b * (Pi.y + 1) = (a * Pi.x + b * Pi.y) + a = ErreurY(Pi.x, Pi.y) + a + b
.
ErreurY(Pi.x + 1, Pi.y) + b = ErreurY(Pi.x + 1, Pi.y + 1)
.
ErreurY(Pi.x + 1, Pi.y) < ErreurY(Pi.x + 1, Pi.y + 1)
puisque b
est négatif.
L'erreur moyenne est donc de -b/2
en valeur absolue. Le principe est alors :
(Pi.x + 1, Pi.y)
.(Pi.x + 1, Pi.y + 1)
.b
après cumul.
L'erreur initiale est initialisée à l'opposée de l'erreur moyenne, b/2
. Elle est négative et elle augmente à chaque itération. Le test de dépassement devient Erreur >= 0
.
DeltaX<DeltaY
.
Pour la dessiner, les pixels sont parcourus de l'ordonnée P1.y
à P2.y
. L'abscisse du premier pixel commence à P1.x
.
Pour un point (Pi.x, Pi.y)
tracé, compte tenu de l'alignement sur la grille des pixels, la question est de savoir si le prochain point à tracer est soit :
(Pi.x, Pi.y + 1)
- On reste sur la même abscisse en descendant.
(Pi.x + 1, Pi.y + 1)
- On se décale à gauche en descendant.
Pour cela, nous évaluons une erreur d'alignement donnée par la distance à la ligne calculée par application de l'équation de la droite :
ErreurX(Pi.x, Pi.y + 1) = a * Pi.x + b * (Pi.y + 1) = (a * Pi.x + b * Pi.y) + b = ErreurX(Pi.x, Pi.y) + b
.
ErreurX(Pi.x + 1, Pi.y + 1) = a * (Pi.x + 1) + b * (Pi.y + 1) = (a * Pi.x + b * Pi.y) + a = ErreurX(Pi.x, Pi.y) + a + b
.
ErreurX(Pi.x, Pi.y + 1) + a = ErreurX(Pi.x + 1, Pi.y + 1)
.
ErreurX(Pi.x, Pi.y + 1) > ErreurX(Pi.x + 1, Pi.y + 1)
puisque a
est positif.
L'erreur moyenne est donc de a/2
en valeur absolue. Le principe est alors :
(Pi.x, Pi.y + 1)
.(Pi.x + 1, Pi.y + 1)
.a
après cumul.
-a/2
. Elle est négative et elle augmente à chaque itération. Le test de dépassement devient Erreur >= 0
.
Procedure DessinerLigne(P1 : Nul Ou Point, P2 : Nul Ou Point)
/***********************************************************/
Variable
/******/
DeltaX : Entier;
DeltaY : Entier;
X : Entier;
X1 : Entier;
X2 : Entier;
Y : Entier;
ProgressionX : Entier;
ProgressionY : Entier;
ErreurX : Entier;
ErreurY : Entier;
CorrectionErreurX : Entier;
CorrectionErreurY : Entier;
Longueur : Entier;
/* Calcul de DeltaY i.e. a. */
DeltaY=P2.Y-P1.Y;
Si DeltaY>0 Alors
ProgressionY=1;
Sinon
ProgressionY=-1;
Fin Si
DeltaY=-DeltaY;
/* Calcul de DeltaX i.e. b. */
DeltaX=P2.X-P1.X;
Si DeltaX>0 Alors
ProgressionX=1;
Sinon
ProgressionX=-1;
Fin Si
DeltaX=-DeltaX;
Si DeltaY==0 Alors
/* Cas trivial de la ligne horizontale. */
X=P1.X;
Fin Si
Y=P1.Y;
Longueur=P2.X-P1.X+1;
DessinerLigneElementaire(X, Y, Longueur);
Retourner;
Si Delta==0 Alors
/* Cas trivial de la ligne verticale. */
X=P1.X;
Fin Si
Pour Y=P1.Y JusquA P2.Y Pas ProgressionY Faire
DessinerPointElementaire(X, Y);
Fin Pour
Retourner;
Si DeltaX>DeltaY Alors
/* Ligne a parcours horizontal. */
ErreurY=-DeltaX/2;
Sinon
X1=P1.X;
X2=P1.X;
Y=P1.Y;
Pour X=P1.X JusquA P2.X Pas ProgressionX Faire
/* Progression dans la ligne. */
Fin Pour
ErreurY+=DeltaY;
Si ErreurY>=0 Alors
/* Changement d'ordonnée. Vidage de la portion de ligne. */
Sinon
Si X1<
Longueur=X2-X1+1;
Sinon
DessinerLigneElementaire(X1, Y, Longueur);
Longueur=X1-X2+1;
X1=P1.X;
DessinerLigneElementaire(X2, Y, Longueur);
X2=P1.X;
Y+=ProgressionY;
ErreurY-=DeltaX;
X2+=ProgressionX;
Fin Si
/* Vidage du résidu de la ligne. */
Si X1<
Longueur=X2-X1+1;
Sinon
DessinerLigneElementaire(X1, Y, Longueur);
Longueur=X1-X2+1;
DessinerLigneElementaire(X2, Y, Longueur);
/* Ligne a parcours vertical. */
ErreurX=-DeltaY/2;
Fin Si
X=P1.X;
Pour Y=P1.Y JusquA P2.Y Pas ProgressionY Faire
DessinerPointElementaire(X, Y);
Fin Pour
/* Progression dans la ligne. */
ErreurX+=DeltaX;
Si ErreurX>=0 Alors
X+=ProgressionX;
Fin Si
ErreurX-=DeltaY;
Fin Procedure
Voici un exemple de quatre lignes épaissent de quatre pixels :
L'épaisseur Epaisseur est répartie de part et d'autre de la ligne sur son orthogonale. Par convention, en cas d'un nombre pair de pixels, il y a une épaisseur de pixels de plus au dessous ou à gauche qu'au dessus ou à droite.
Nous supposons toujours que :
Chaque point à dessiner de l'algorithme DessinerLigne devient un ensemble d'épaisseurs à dessiner. Ces dernières se décalent de façon orthogonale au mouvement du curseur au point (X, Y)
de la ligne. Ainsi, il y a un changement d'épaisseur quand il y a une variation de X
.
Dans un premier temps, les épaisseurs de référence EpaisseursReference sont calculées à partir du principe de l'algorithme DessinerLigne à partir du point fictif (0, 0)
.
Le nombre d'épaisseurs est calculé avec la relation de Pythagore, à savoir X^2 + Y^2 = EpaisseurLigne^2
.
En pratique, du fait des nombres entiers, nous minimisons |X^2 + Y^2 - EpaisseurLigne^2|
. Le test d'arrêt est l'atteinte de ce minimum.
Ainsi, EpaisseursReference et EpaisseursADessiner sont des tableaux de taille au plus Epaisseur éléments.
Le tableau EpaisseursADessiner est exploité par rotation de l'index Index. Une place est libérée en traçant la plus veille épaisseur avant de mémoriser la plus récente.
Type Epaisseur Defaut
/*******************/
X1 : Entier;
Fin Type
X2 : Entier;
Y : Entier;
Constructeur();
Procedure DessinerLigne(P1 : Nul Ou Point, P2 : Nul Ou Point, EpaisseurLigne : Entier=1)
/**************************************************************************************/
Variable
/******/
DeltaX : Entier;
DeltaY : Entier;
X : Entier;
X1 : Entier;
X2 : Entier;
Y : Entier;
ProgressionX : Entier;
ProgressionY : Entier;
ErreurX : Entier;
ErreurY : Entier;
CorrectionErreurX : Entier;
CorrectionErreurY : Entier;
NbReferences : Entier;
EpaisseursReference : Nul Ou TableauDe Epaisseur;
EpaisseursADessiner : Nul Ou TableauDe Epaisseur;
EpaisseurCarre : Entier;
Epaisseur1 : Entier;
Epaisseur2 : Entier;
Longueur : Entier;
Index : Entier;
IndexPrecedent : Entier;
Distance : Entier;
DistancePrecedente : Entier;
DecalageX : Entier;
DecalageY : Entier;
I : Entier;
/* Calcul de DeltaY i.e. de a. */
DeltaY=P2.Y-P1.Y;
Si DeltaY>0 Alors
ProgressionY=1;
Sinon
ProgressionY=-1;
Fin Si
DeltaY=-DeltaY;
/* Calcul de DeltaX i.e. de b. */
DeltaX=P2.X-P1.X;
Si DeltaX>0 Alors
ProgressionX=1;
Sinon
ProgressionX=-1;
Fin Si
DeltaX=-DeltaX;
Si DeltaY==0 Alors
/* Cas trivial de la ligne horizontale. */
Epaisseur1=EpaisseurLigne/2;
Fin Si
Epaisseur2=EpaisseurLigne-Epaisseur1;
Si P1.X<P2.X Alors
X=P1.X;
Sinon
Longueur=P2.X-P1.X;
X=P2.X;
Fin Si
Longueur=P1.X-P2.X;
Y=P1.Y;
Pour Y=P1.Y-Epaisseur1 JusquA P1.Y+Epaisseur2 Pas 1 Faire
DessinerLigneElementaire(X, Y, Longueur);
Fin Pour
Retourner;
Si Delta==0 Alors
/* Cas trivial de la ligne verticale. */
X=P1.X-EpaisseurLigne/2;
Fin Si
Longueur=EpaisseurLigne;
Pour Y=P1.Y JusquA P2.Y Pas ProgressionY Faire
DessinerLigneElementaire(X, Y, Longueur);
Fin Pour
Retourner;
EpaisseursReference=Tableau(Epaisseur, EpaisseurLigne, Epaisseur());
EpaisseursADessiner=Tableau(Epaisseur, EpaisseurLigne, Epaisseur());
EpaisseurCarre=EpaisseurLigne*EpaisseurLigne;
/* Calcul des references des epaisseurs. */
X=0;
Y=0;
DistancePrecedente=EpaisseurCarre;
Si DeltaX>DeltaY Alors
/* Ligne a parcours horizontal. */
NbReferences=-1;
Sinon
ErreurY=-DeltaX/2;
TantQue Vrai Faire
/* Calcul de la distance. */
Fin Pour
Distance=X^2 + Y^2 - EpaisseurCarre;
Si Distance<0 Alors
Distance=-Distance;
Fin Si
Si Distance>DistancePrecedente Alors
/* Le minimum de la distance est atteint. */
Arreter;
Fin Si
Distance=DistancePrecedente;
EpaisseursReference[++NbReferences].X1=-Y;
EpaisseursReference[NbReferences].X2=-Y;
EpaisseursReference[NbReferences].Y=X;
/* Progression dans la ligne. */
ErreurY+=DeltaY;
Si ErreurY>=0 Alors
Y+=ProgressionY;
Fin Si
ErreurY-=DeltaX;
X+=ProgressionX;
/* Ligne a parcours vertical. */
NbReferences=0;
Fin Si
EpaisseursReference[0].X1=0;
EpaisseursReference[0].X2=0;
EpaisseursReference[0].Y=0;
ErreurX=-DeltaY/2;
TantQue Vrai Faire
/* Calcul de la distance. */
Fin TantQue
Distance=X^2 + Y^2 - EpaisseurCarre;
Si Distance<0 Alors
Distance=-Distance;
Fin Si
Si Distance>DistancePrecedente Alors
/* Le minimum de la distance est atteint. */
Arreter;
Fin Si
Distance=DistancePrecedente;
/* Progression dans la ligne. */
ErreurX+=DeltaX;
Si ErreurX>=0 Alors
EpaisseursReference[++NbReferences].X1=-Y;
Sinon
EpaisseursReference[NbReferences].X2=-Y;
EpaisseursReference[NbReferences].Y=X;
X+=ProgressionX;
ErreurX-=DeltaY;
EpaisseursReference[NbReferences].X2+=ProgressionX;
Fin Si
Y+=ProgressionY;
/* Repartition des epaisseurs de part et d'autre de la ligne. */
Si NbReferences%2 Alors
Index=(NbReferences/2)+1;
Sinon
Index=NbReferences/2;
Fin Si
DecalageX=(EpaisseursReference[Index].X1+EpaisseursReference[Index].X2)/2;
DecalageY=EpaisseursReference[Index].Y/2;
Pour Index=0 JusquA NbReferences Pas 1 Faire
EpaisseursReference[Index].X1-=DecalageX;
Fin Pour
EpaisseursReference[Index].X2-=DecalageX;
EpaisseursReference[Index].Y-=DecalageY;
/* Parcours de la ligne. */
Si DeltaX>DeltaY Alors
/* Ligne a parcours horizontal. */
ErreurY=-DeltaX/2;
Sinon
X=P1.X;
Y=P1.Y;
/* Premieres epaisseurs. */
Pour Index=0 JusquA NbReferences Pas 1 Faire
EpaisseursADessiner[Index].X1=X+EpaisseursReference[Index].X1;
Fin Pour
EpaisseursADessiner[Index].X2=X+EpaisseursReference[Index].X2;
EpaisseursADessiner[Index].Y=Y+EpaisseursReference[Index].Y;
IndexPrecedent=-1;
Pour X=P1.X JusquA P2.X Pas ProgressionX Faire
/* Progression dans la ligne. */
Fin Pour
ErreurY+=DeltaY;
Si ErreurY>=0 Alors
/* Libere de la place pour la nouvelle epaisseur en vidant la plus ancienne. */
SinonSi IndexPrecedent==-1 Alors
Si IndexPrecedent==NbReferences Alors
Index=0;
Sinon
Index=IndexPrecedent+1;
Fin Si
Si EpaisseursADessiner[Index].X1<EpaisseursADessiner[Index].X2 Alors
Longueur=EpaisseursADessiner[Index].X2-EpaisseursADessiner[Index].X1+1;
Sinon
DessinerLigneElementaireHorizontale(EpaisseursADessiner[Index].X1, EpaisseursADessiner[Index].Y, Longueur);
Longueur=EpaisseursADessiner[Index].X1-EpaisseursADessiner[Index].X2+1;
Fin Si
DessinerLigneElementaireHorizontale(EpaisseursADessiner[Index].X2, EpaisseursADessiner[Index].Y, Longueur);
Y+=ProgressionY;
ErreurY-=DeltaX;
Si NbReferences>0 Alors
/* Concatenation des epaisseurs precedentes et des courantes. */
IndexPrecedent=Index;
Fin Si
Pour I=0 JusquA NbReferences-1 Pas 1 Faire
Si IndexPrecedent==NbReferences Alors
Fin Pour
IndexPrecedent=0;
Sinon
IndexPrecedent++;
Fin Si
X1=X+EpaisseursReference[I].X1;
Si EpaisseursADessiner[IndexPrecedent].X1>X1 Alors
EpaisseursADessiner[IndexPrecedent].X1=X1;
Fin Si
X2=X.EpaisseursReference[I].X2;
Si EpaisseursADessiner[IndexPrecedent].X2<X2 Alors
EpaisseursADessiner[IndexPrecedent].X2=X2;
Fin Si
/* Ajout de la nouvelle epaisseur. */
EpaisseursADessiner[Index].X1=X+PReference[NbReferences].X1;
EpaisseursADessiner[Index].X2=X+PReference[NbReferences].X2;
EpaisseursADessiner[Index].Y=Y+PReference[NbReferences].Y;
IndexPrecedent=Index;
IndexPrecedent=NbReferences;
Sinon
/* Prolongation des epaisseurs precedentes. */
Fin Si
Pour I=0 JusquA NbReferences Pas 1 Faire
EpaisseursADessiner[Index].X2+=ProgressionX;
Fin Pour
/* Ligne a parcours vertical. */
ErreurX=-DeltaY/2;
Fin Si
X=P1.X;
IndexPrecedent=-1;
Pour Y=P1.Y JusquA P2.Y Pas ProgressionY Faire
Si IndexPrecedent==-1 Alors
Fin Pour
/* Premieres epaisseurs. */
Sinon
Pour Index=0 JusquA NbReferences Pas 1 Faire
EpaisseursADessiner[Index].X1=X+EpaisseursReference[Index].X1;
Fin Pour
EpaisseursADessiner[Index].X2=X+EpaisseursReference[Index].X2;
EpaisseursADessiner[Index].Y=Y+EpaisseursReference[Index].Y;
IndexPrecedent=NbReferences;
/* Libere de la place pour la nouvelle epaisseur en vidant la plus ancienne. */
Fin Si
Si IndexPrecedent==NbReferences Alors
Index=0;
Sinon
Index=IndexPrecedent+1;
Fin Si
Si EpaisseursADessiner[Index].X1<EpaisseursADessiner[Index].X2 Alors
Longueur=EpaisseursADessiner[Index].X2-EpaisseursADessiner[Index].X1+1;
Sinon
DessinerLigneElementaireHorizontale(EpaisseursADessiner[Index].X1, EpaisseursADessiner[Index].Y, Longueur);
Longueur=EpaisseursADessiner[Index].X1-EpaisseursADessiner[Index].X2+1;
Fin Si
DessinerLigneElementaireHorizontale(EpaisseursADessiner[Index].X2, EpaisseursADessiner[Index].Y, Longueur);
Si NbReferences>1 Alors
/* Concatenation des epaisseurs precedentes et des courantes. */
IndexPrecedent=Index;
Fin Si
Pour I=0 JusquA NbReferences-1 Pas 1 Faire
Si IndexPrecedent==NbReferences Alors
Fin Pour
IndexPrecedent=0;
Sinon
IndexPrecedent++;
Fin Si
X1=X+EpaisseursReference[I].X1;
Si EpaisseursADessiner[IndexPrecedent].X1>X1 Alors
EpaisseursADessiner[IndexPrecedent].X1=X1;
Fin Si
X2=X.EpaisseursReference[I].X2;
Si EpaisseursADessiner[IndexPrecedent].X2<X2 Alors
EpaisseursADessiner[IndexPrecedent].X2=X2;
Fin Si
/* Ajout de la nouvelle epaisseur. */
EpaisseursADessiner[Index].X1=X+EpaisseursReference[NbReferences].X1;
EpaisseursADessiner[Index].X2=X+EpaisseursReference[NbReferences].X2;
EpaisseursADessiner[Index].Y=Y+EpaisseursReference[NbReferences].Y;
IndexPrecedent=Index;
/* Progression dans la ligne. */
ErreurX+=DeltaX;
Si ErreurX>=0 Alors
X+=ProgressionX;
Fin Si
ErreurX-=DeltaY;
/* Vidage des epaisseurs residuelles. */
Pour I=0 JusquA NbReferences Pas 1 Faire
Si IndexPrecedent==NbReferences Alors
Fin Pour
IndexPrecedent=0;
Sinon
IndexPrecedent++;
Si EpaisseursADessiner[Index].X1<EpaisseursADessiner[Index].X2 Alors
Longueur=EpaisseursADessiner[Index].X2-EpaisseursADessiner[Index].X1+1;
Sinon
DessinerLigneElementaireHorizontale(EpaisseursADessiner[Index].X1, EpaisseursADessiner[Index].Y, Longueur);
Longueur=EpaisseursADessiner[Index].X1-EpaisseursADessiner[Index].X2+1;
Fin Si
DessinerLigneElementaireHorizontale(EpaisseursADessiner[Index].X2, EpaisseursADessiner[Index].Y, Longueur);
IndexPrecdent=Index;
Fin Procedure