★ CODING ★ ANTIBUG ★ LE TRACÉ DE LIGNES SUR CPC ET Z80 ★ |
Le tracé de lignes sur CPC et Z80 | Coding Antibug |
Sommaire:dernière maj : le 13/09/2007
Dans cet article nous allons étudier le tracé de lignes en fonction du mode graphique utilisé sur le CPC. Si vous ne savez pas comment la mémoire graphique du CPC est organisée et si vous ne savez pas comment afficher un pixel à l'écran, je vous conseille de consulter mon article précédent sur « La mémoire graphique du CPC ». Dans cet article j'ai détaillé l'organisation des octets et comment sont calculées les adresses de bases des lignes. Pour tous les calculs de multiplication ou de division, je vais faire référence à des routines détaillées dans un article sur les opérations avec le Z80. Je vous conseille de vous reporter à l'article « Les Multiplications et les Divisions avec le Z80 ». Pour commencer, nous allons voir comment sont tracées les lignes horizontales, les lignes verticales et pour terminer les lignes obliques. 2 - Calcul de l'adresse de base d'une ligne et d'un point Pour commencer, il faut savoir que les lignes horizontales et les lignes verticales sont les plus rapides à afficher et que le calcul est très limité. En effet, avant de tracer une ligne la première chose à faire est de calculer l'adresse de base de la ligne (en fonction de la coordonnée Y), puis de calculer ensuite l'adresse du premier point en fonction de l'adresse de la ligne ; vous pouvez ensuite afficher l'ensemble des points séquentiellement. Regardons en semble un exemple concret pour un jeu de coordonnées (X1,Y1)(X2,Y2): 2.1 Calcul de l'adresse de base de la ligne [Adresse de base de la ligne] = [Adresse vidéo] + [Adresse de la série] + [Offset de la ligne] * &800 |
Pixel | Octet | Couleur entrelacée | Masque de préservation |
0 | 0 | %00101010 | %01010101 |
1 | 0 | %00010101 | %10101010 |
2 | 1 | %00101010 | %01010101 |
3 | 1 | %00010101 | %10101010 |
4 | 2 | %00101010 | %01010101 |
5 | 2 | %00010101 | %10101010 |
Si vous regardez bien ce tableau vous pouvez constatez que le masque des données de la couleur entrelacée est décalé de 1 bit vers la droite à chaque octet traité, 2 valeurs possibles car 2 pixels à afficher logique ! Même si vous ne trouvez pas cet exemple essentiel nous allons regarder le cas du mode 1 (4 couleurs, 4 pixels par octet).
En mode 1 (4 couleurs, 4 pixels par octet) :
Affichons maintenant une ligne horizontale de la position X1 = 0 à la position X2 = 3 avec la couleur 3.
Masques logiques initiaux associés au premier pixel (0) :
- masque des données entrelacées de la couleur (7) : %00101010
- masque de préservation du pixel voisin : %01010101
Valeurs des masques pendant l'affichage des pixels suivants :
Pixel | Octet | Couleur entrelacée | Masque de préservation |
1 | 0 | %01000100 | %10111011 |
2 | 0 | %00100010 | %11011101 |
3 | 0 | %00010001 | %11101110 |
4 | 1 | %10001000 | %01110111 |
5 | 1 | %01000100 | %10111011 |
6 | 1 | %00100010 | %11011101 |
7 | 1 | %00010001 | %11101110 |
8 | 2 | %10001000 | %01110111 |
9 | 2 | %01000100 | %10111011 |
10 | 2 | %00100010 | %11011101 |
11 | 2 | %00010001 | %11101110 |
Comme dans l'exemple précédent, le masque logique associé à la couleur est décalé vers la droite de 1 bit pour chaque pixel allumé ; au passage, le masque de préservation des données voisines aussi. Tout çà juste pour vous dire que notre algorithme doit prendre en compte la position du premier pixel pour calculer les masques logiques et les décalages suivants.
Imaginons maintenant que notre ligne horizontale commence à la position 1 et pas à la position 0, pour traiter un octet complet, c.à.d. les 2 derniers pixels de l'octet, nous devrions décaler encore de 2 positions les masques logiques. Et bien voilà !
Regardons maintenant notre algorithme :
Affichage d'une ligne de X1 à X2 à la ligne Y avec la couleur c
On a :
Voilà pour l'algorithme. Cet algorithme n'est pas compliqué mais comme vous le savez les registres du Z80 étant peu nombreux il va falloir le coder notre routine le plus proprement possible.
Pour aborder facilement la routine, il faut savoir que celle-ci est décomposée en plusieurs parties bien distinctes, le calcul du nombre de points à traiter, le calcul de l'adresse du premier pixel (déjà archi vue !), le calcul des masques initiaux (fonction de la position du premier pixel), affichage du point à la position courante, calcul des masques logiques pour le point suivant, affichage du point à la position courante, puis …, puis …, puis on répète !
Regardons ensemble le contenu de la procédure d'affichage d'une ligne en 16 couleurs…
4.3 Affichage d'une ligne horizontale en mode 0 (16 couleurs, 2 pixels par octet)
PUSH AF ; Sauvegarde des registres
PUSH BC ;
PUSH DE ; AF,BC,DE,HL,IX,IY
PUSH HL ;
PUSH IX ;
PUSH IY ;
; Calcul de Dx (nombre de points à traiter)
LD E, A ; La couleur dans E
RLA ; On efface Carry
SBC HL, BC ; HL = X2 - X1
PUSH HL ; Sauvegarde du nombre de points restant à traiter
; Adresse du premier pixel
;
; En entrée
; BC <=> X1
; A <=> Y
; En sortie
; HL <=> Adresse du point
;
LD A, D ; Y (D) dans A
; BC contient déjà X1
CALL GRAPH_ADR_OF_PIXEL
PUSH HL
POP IX
POP HL
; Préparation des masques logiques en fonction du n° de la couleur
LD A, D ; Sauvegarde de Y dans A
LD D, 0 ; On efface D
LD IY, DEF_GRAPH_TABLE_PIXEL_16 ; Référence à la table des masques 16 couleurs
ADD IY, DE
LD E, (IY) ; Masque logique associé à la couleur entrelacée dans B
LD D, 85 ; Masque logique permettant de préserver le pixel voisin dans D
; (voir affichage d'un pixel en mode 0)
; Décalage des masques en fonction du nombre de points déjà traités
LD B, 2
LD A, C
AND 1
OR A
JP z, _GRAPH_LINE_H_16_1
DEC B
SRL E
SLA D
_GRAPH_LINE_H_16_1
; Affichage du pixel courant
LD A, (IX)
AND D ; Préservation des données du pixel voisin
OR E ; Affichage du nouveau pixel
LD (IX), A
LD A, L ; On teste si tous les points on été traités
OR H ;
JP z, _GRAPH_LINE_H_16_END
DEC HL ; On décrémente le nombre de points restant à traiter
DJNZ _GRAPH_LINE_H_16_2
LD B, 2
INC IX ; Point suivant
LD E, (IY) ; Masque logique associé à la couleur entrelacée dans B
LD D, 85 ; Masque logique permettant de préserver le pixel voisin dans D
; (voir affichage d'un pixel en mode 0)
JP _GRAPH_LINE_H_16_1
_GRAPH_LINE_H_16_2
SRL E
SLA D
JP _GRAPH_LINE_H_16_1
_GRAPH_LINE_H_16_END
POP IY ; Restauration des registres
POP IX ;
POP HL ; AF,BC,DE,HL,IX,IY
POP DE ;
POP BC ;
POP AF ;
RET
4.4 Affichage d'une ligne horizontale en mode 1 (4 couleurs, 4 pixels par octet)
PUSH AF ; Sauvegarde des registres
PUSH BC ;
PUSH DE ; AF,BC,DE,HL,IX, IY
PUSH HL ;
PUSH IX ;
PUSH IY ;
; Calcul de Dx (nombre de points à traiter)
LD E, A ; La couleur dans E
RLA ; On efface Carry
SBC HL, BC ; HL = X2 - X1
PUSH HL ; Sauvegarde du nombre de points restant à traiter
; Adresse du premier pixel
;
; En entrée
; BC <=> X1
; A <=> Y
; En sortie
; HL <=> Adresse du point
;
; BC est déjà chargé avec sa valeur
;
LD A, D ; Y dans D
;
CALL GRAPH_ADR_OF_PIXEL
PUSH HL ; On stocke HL dans IX pour accéder à la mémoire écran
POP IX ;
POP HL ; Restauration du nombre de points à traiter dans HL
; Préparation des masques logiques en fonction du n° de la couleur
LD D, 0 ; On efface D
LD IY, DEF_GRAPH_TABLE_PIXEL_4 ; Référence à la table des masques 16 couleurs
ADD IY, DE
; Vérification à partir de quel pixel doit commencer le tracé
LD A, C ; C contient la partie base de X1
AND 3 ; On ne préserve que les 3 premiers bits qui correspondent
; au n° du point
LD C, A ; Calcul du nombre de pixel restant à traiter, inclu le premier pixel
LD A, 4 ;
SUB C ; [Nombre de pixele à traiter] = 4 - [n° du pixel]
LD B, A ;
LD A, C ; Nombre de déclages a effectuer pour calculer les masques logiques
; associés au premier pixel
; Décalages des masques vers le bas en fonction du n° du pixel concerné
;
; Paramètres de la fonction MATH_SRL_8
; En entrée
; E <=> Entier de 8 bits
; A <=> Nombre de bits à décaler
; En sortie
; E <=> Résultat du décalage des n bits
;
; A et E sont chargés avec leur valeur respective
LD E, (IY) ; Masque logique associé à la couleur entrelacée dans B
CALL MATH_SRL_8 ; Décalage du masque de la couleur entrelacée, associée à la couleur
; du pixel
; En sortie E contient le masque de la valeur entrelacée associé à la
; couleur du pixel
LD C, E ; Sauvegarde du masque entrelacé E
; A est déjà chargé avec le nombre de décalages
LD E, 136 ; Valeur par défaut du masque logique qui va permettre de préserver
; le pixel voisin, en Mode 1 = 136 <=> %10001000
CALL MATH_SRL_8 ; Décalage du masque négatif qui permet de préserver le pixel voisin
; Inversion du masque logique négatif de manière à préserver les données du pixel
; voisin
LD D, E ; Sauvegarde du masque décalé qui permet de préserver les pixels voisin
LD A, E ; Inversion du masque pour la préservations des pixels voisins
CPL ;
LD E, C ; Restauration de E qui doit contenir le masque entrelacé lié à la
; couleur du pixel
LD C, A ; C va contenir le masque logique inversé qui permet la préservation
; les pixels voisins
_GRAPH_LINE_H_4_1
; Affichage du pixel courant
LD A, (IX)
AND C ; Préservation des données du pixel voisin
OR E ; Affichage du nouveau pixel
LD (IX), A
LD A, L ; On teste si tous les points on été traités
OR H ;
JP z, _GRAPH_LINE_H_4_END
DEC HL ; On décrémente le nombre de points restant à traiter
DJNZ _GRAPH_LINE_H_4_2
LD B, 4
INC IX ; Point suivant
LD E, (IY) ; Masque logique associé à la couleur entrelacée dans B
LD D, 136 ; Valeur par défaut du masque logique qui va permettre de préserver
; le pixel voisin, en Mode 1 = 136 <=> %10001000
LD C, 119 ; Le masque logique inversé
JP _GRAPH_LINE_H_4_1
_GRAPH_LINE_H_4_2
SRL E
SRL D
LD A, D
CPL
LD C, A
JP _GRAPH_LINE_H_4_1
_GRAPH_LINE_H_4_END
POP IY ; Restauration des registres
POP IX ;
POP HL ; AF,BC,DE,HL,IX,IY
POP DE ;
POP BC ;
POP AF ;
RETRegardons maintenant en détail toutes les parties de cette routine :
1 : Calcul du nombre de points à traiter :
En entrée, le registre A contient la couleur, vous pourriez me dire pourquoi ne pas directement la couleur dans le registre E sachant que de toute façon c'est E qui contient le couleur ? Et bien tout simplement pour garder une homogénéité entre les appels de méthodes ; toutes les méthodes de tracé horizontal comportent les même paramètres en entrée car elles sont utilisées pour l'affichage de cadres … enfin bref !
RLA permet d'effacer Carry pour la soustraction suivante.
HL – BC = le nombre de points à traiter.
2 : Calcul de l'adresse du premier pixel :
Comme vous pouvez le voir l'utilisation de la procédure GRAPH_ADR_OF_PIXEL est requise, les paramètres de cette procédure sont indiqués en entrée, jusqu'ici rien de compliqué ! La procédure va nous renvoyer l'adresse du point dans HL, on va l'échanger avec IX via un PUSH/POP car HL va nous servir plus loin pour calculer le nombre de point restant.
3 : Calcul des masques logiques initiaux :
Maintenant nous allons calculer les masques logiques pour l'affichage de notre premier point. D contient y, nous le sauvons via A pour la suite sachant que nous allons avoir besoin de DE pour obtenir l'adresse de la table des données entrelacées des couleurs. L'adresse de la table est stockée dans IY, on ajouter ensuite le décalage en fonction de la couleur contenue dans E sachant que les données de la table sont triées de la couleur 0 à la couleur 15. IY va donc pointer vers les données entrelacées de la couleur de notre ligne ; IY va nous servir pendant le calcul des autres points. Pour finir on transfert la couleur entrelacée à l'adresse (IY) dans E et l'on place dans D le masque logique qui permet de préserver les données du pixel voisin.
4 : Calcul des masques logiques du premier point :
DEC B
SRL E
SLA D
_GRAPH_LINE_H_16_1
Rappelez-vous qu'en mode 0, 16 couleurs on a 2 pixels par octet. Ici B va contenir le nombre de pixels par octet (2 en 16 couleurs, 4 en 4 couleurs et 8 en 2 couleurs). On décrémente B de 1 en fonction du n° du premier pixel qui est affiché ; les valeurs par défaut des masques sont toujours celles du premier pixel de l'octet, il faut donc décaler ces masque en fonction du n° du pixel courant dans l'octet. Si le premier pixel est le premier d'une série, (A AND 1) = 0, B n'est pas décrémenté et les masques ne sont pas décalés, on saute donc à _GRAPH_LINE_H_16_1.
5 : Affichage du pixel courant :
L'affichage d'un pixel n'est pas compliqué, IX contient l'adresse du pixel à afficher, on charge donc A avec la valeur de l'octet à l'adresse (IX), on applique ensuite le masque de préservation des données du pixel voisin, (A AND D) ; on applique la valeur entrelacée de la couleur de notre nouveau point, (A OR E). Pour terminer, on remplace ensuite la valeur de notre nouvel pour afficher notre point.
6 : Calcul du nombre de points restant :
Rappelez-vous que HL contient le nombre total de points restant à traiter. HL est décrémenté à chaque fois qu'un point est affiché, on va donc vérifier que tous les points ont été traités, si tel est le cas on sort de la boucle principale via un saut à _GRAPH_LINE_H_16_END, si il nous reste des points à traiter, on va décrémenter B via DJNZ, B contient le nombre de points restant à traiter par octet. Si B = 0 c'est que tous les points de l'octet courant ont été traités.
7 : Vérification que tous les points de l'octet courant ont été traités :
_GRAPH_LINE_H_16_2
SRL E
SLA D
JP _GRAPH_LINE_H_16_1
_GRAPH_LINE_H_16_END
Dernière partie ! Ouf !
Si tous les points on été traités, on réinitialise B avec le nombre de points par octet LD B, 2. IX est incrémenté de 1 car on passe à l'octet suivant, on réinitialise les masques avec les valeurs par défaut du premier pixel du nouvel octet puis on saute à _GRAPH_LINE_H_16_1 pour aller afficher le nouveau pixel.
Dans le cas où il restait des points à traiter dans l'octet courant, en _GRAPH_LINE_H_16_2, on décale les masques logiques d' 1 bit vers la droite pour traiter le pixel suivant, puis l'on saute en _GRAPH_LINE_H_16_1 pour affiche ce pixel.
Voilà ! J'espère que c'est un peu plus clair pour vous.
Les exemples suivant sont basés sur la même structure de codage, la seule différence est que l'on va utiliser la procédure MATH_SRL_8, dans le calcul des masques du premier pixel, pour pouvoir décaler les masques de plusieurs bits à la fois.
Cette première partie est terminée. Le mois prochain nous traiterons le tracé vertical et oblique, @ Bientôt …
4.5 Affichage d'une ligne horizontale en mode 2 (2 couleurs, 8 pixels par octet)
GRAPH_LINE_H_2:
PUSH AF ; Sauvegarde des registres
PUSH BC ;
PUSH DE ; AF,BC,DE,HL,IX, IY
PUSH HL ;
PUSH IX ;
PUSH IY ;
; Calcul de Dx (nombre de points à traiter)
LD E, A ; La couleur dans E
RLA ; On efface Carry
SBC HL, BC ; HL = X2 - X1
PUSH HL ; Sauvegarde du nombre de points restant à traiter
; Adresse du premier pixel
;
; En entrée
; BC <=> X1
; A <=> Y
; En sortie
; HL <=> Adresse du point
;
; BC est déjà chargé avec sa valeur
;
LD A, D ; Y dans D
;
CALL GRAPH_ADR_OF_PIXEL
PUSH HL ; On stocke HL dans IX pour accéder à la mémoire écran
POP IX ;
POP HL ; Restauration du nombre de points à traiter dans HL
; Préparation des masques logiques en fonction du n° de la couleur
LD D, 0 ; On efface D
LD IY, DEF_GRAPH_TABLE_PIXEL_2 ; Référence à la table des masques 16 couleurs
ADD IY, DE
; Vérification à partir de quel pixel doit commencer le tracé
LD A, C ; C contient la partie base de X1
AND 7 ; On ne préserve que les 7 premiers bits qui correspondent
; au n° du point
LD C, A ; Calcul du nombre de pixel restant à traiter, inclu le premier pixel
LD A, 8 ;
SUB C ; [Nombre de pixele à traiter] = 8 - [n° du pixel]
LD B, A ;
LD A, C ; Nombre de déclages a effectuer pour calculer les masques logiques
; associés au premier pixel
; Décalages des masques vers le bas en fonction du n° du pixel concerné
;
; Paramètres de la fonction MATH_SRL_8
; En entrée
; E <=> Entier de 8 bits
; A <=> Nombre de bits à décaler
; En sortie
; E <=> Résultat du décalage des n bits
;
; A et E sont chargés avec leur valeur respective
LD E, (IY) ; Masque logique associé à la couleur entrelacée dans B
CALL MATH_SRL_8 ; Décalage du masque de la couleur entrelacée, associée à la couleur
; du pixel
; En sortie E contient le masque de la valeur entrelacée associé à la
; couleur du pixel
LD C, E ; Sauvegarde du masque entrelacé E
; A est déjà chargé avec le nombre de décalages
LD E, 128 ; Valeur par défaut du masque logique qui va permettre de préserver
; le pixel voisin, en Mode 2 = 128 <=> %10000000
CALL MATH_SRL_8 ; Décalage du masque négatif qui permet de préserver le pixel voisin
; Inversion du masque logique négatif de manière à préserver les données du pixel
; voisin
LD D, E ; Sauvegarde du masque décalé qui permet de préserver les pixels voisin
LD A, E ; Inversion du masque pour la préservations des pixels voisins
CPL ;
LD E, C ; Restauration de E qui doit contenir le masque entrelacé lié à la
; couleur du pixel
LD C, A ; C va contenir le masque logique inversé qui permet la préservation
; les pixels voisins
_GRAPH_LINE_H_2_1
; Affichage du pixel courant
LD A, (IX)
AND C ; Préservation des données du pixel voisin
OR E ; Affichage du nouveau pixel
LD (IX), A
LD A, L ; On teste si tous les points on été traités
OR H ;
JP z, _GRAPH_LINE_H_2_END
DEC HL ; On décrémente le nombre de points restant à traiter
DJNZ _GRAPH_LINE_H_2_2
LD B, 8
INC IX ; Point suivant
LD E, (IY) ; Masque logique associé à la couleur entrelacée dans B
LD D, 128 ; Valeur par défaut du masque logique qui va permettre de préserver
; le pixel voisin, en Mode 1 = 128 <=> %10000000
LD C, 127 ; Le masque logique inversé
JP _GRAPH_LINE_H_2_1
_GRAPH_LINE_H_2_2
SRL E
SRL D
LD A, D
CPL
LD C, A
JP _GRAPH_LINE_H_2_1
_GRAPH_LINE_H_2_END
POP IY ; Restauration des registres
POP IX ;
POP HL ; AF,BC,DE,HL,IX,IY
POP DE ;
POP BC ;
POP AF ;
RET
4.6 Affichage d'une ligne horizontale indépendamment de mode graphique
GRAPH_LINE_H:
PUSH AF ; Sauvegarde de AF pour le retour
; Vérification du mode graphique en cours
LD A, CST_GRAPH_VIDEO_MODE
CP 2
JP z, _GRAPH_LINE_H_2
CP 1
JP z, _GRAPH_LINE_H_4
OR A
JP z, _GRAPH_LINE_H_16
; Retour si le mode graphique est <> 0,1 ou 2
POP AF
RET
; Affichage des pixels
;
; En entrée les fonctions possèdent les même paramètres
; BC <=> X1
; HL <=> X2
; D <=> Y
; A <=> Couleur de la ligne
_GRAPH_LINE_H_16
; Affichage de la ligne horizontale 16 couleurs
POP AF
CALL GRAPH_LINE_H_16
RET
_GRAPH_LINE_H_4
; Affichage de la ligne horizontale 4 couleurs
POP AF
CALL GRAPH_LINE_H_4
RET
_GRAPH_LINE_H_2
; Affichage de la ligne horizontale 2 couleurs
POP AF
CALL GRAPH_LINE_H_2
RET
GRAPH_LINE_H_END
5 - Le tracé d'une ligne verticale
L'avantage du tracé des lignes verticales, comme les lignes horizontales, est que l'on n'a pas besoin de recalculer l'adresse de base des lignes à chaque fois que l'on veut afficher un point. En effet sachant qu'une ligne verticale est toujours située sur la même abscisse x et que l'adresse de base d'une ligne est fonction de (x,y), un seul calcul sera nécessaire.
Dans un premier temps, nous allons étudier à quels problèmes nous allons être confronté lorsque nous allons afficher nos pixels. Dans un second temps nous allons mettre un algorithme en place puis pour terminer nous allons coder cet algorithme.
5.2 L'algorithme de tracé verticale
Regardons toutes les étapes pour créer notre algorithme
1 : Calculer l'adresse du premier point
2 : Calculer le nombre de points à traiter
3 : Calculer les masques logiques de notre premier point en fonction de sa position
4 : Affichage de notre point
5 : Calcul de la position du point suivant en fonction de la série de lignes, courante
6 : Traitement du pixel suivant …
Pas compliqué, c'est presque comme le tracé d'une ligne horizontale !
Souvenez-vous, les données des pixels sont entrelacées ; dans notre cas les masques utilisés pour afficher nos pixels sont toujours identiques car les pixels à afficher se trouvent toujours sur la même abscisse. Comme l'affichage d'une ligne horizontale, le calcul le plus compliqué est le calcul de l'adresse de basse du point. L'adresse de base n'est calculée qu'une seule fois au départ, donc ce qui nous reste à régler c'est le calcul des masque logiques. Souvenez-vous, si vous avez lu mon article sur l'organisation de la mémoire graphique, vous savez que les lignes sont regroupées par séries de 8 lignes. En mémoire, chaque ligne d'une série est distante de l'autre d'un offset de &800, pour passer d'une série à une autre, on incrémente l'adresse de base de &50.
Regardons maintenant comment les lignes et les données des pixels sont ordonnées en fonction du mode graphique utilisé et comment sont représentés les masques logiques …
Si vous regardez bien, pour passer de la ligne 0 à la ligne 8 on ajoute &50 à l'adresse de base de la ligne, cela permet de passer d'une série de lignes à une autre. Vous pouvez remarquez que la distance entre deux lignes fait bien &800.
Tout cela pour vous dire que notre algorithme devra prendre en compte le passage d'une ligne à l'autre et d'une série à une autre. Notre algorithme devra d'abord calculer le nombre total de points à traiter, le nombre de lignes restant à traiter dans la série courante puis calculer tous les décalages d'offset entre chaque ligne en fonction du numéro de la ligne courante et le passage d'une série de lignes à une autre.
Regardons maintenant quelles valeurs les masques logiques pourront prendre en fonction du mode graphique et de la position du pixel à afficher.
Prenons comme premier exemple l'affichage d'un pixel bleu en mode 0, 16 couleurs (couleur n°1 par défaut), à la position X = 1, Y1 = 0 et Y2 = 4 ; voici comment devrait être affichés les pixels dans la mémoire :
Comme vous pouvez le voir, notre pixel bleu est stocké dans l'octet 0 de la mémoire graphique. La table ci-dessous reprend les valeurs des masques logiques des couleurs pour les pixels pairs, dont les données sont entrelacées. Dans notre cas la valeur du masque logique pour coder la couleur bleue devra être décalée d'un bit vers la droite, sa valeur sera %00000001. Comme vous vous en douter, ce masque sera identique pour tous les pixels à afficher sachant que X ne va pas varier. (Les masques de la table sont les masques par défaut du pixel 0).
Table des masques logiques pour le mode 0 (16 couleurs)
Comme second exemple nous allons traiter l'affichage d'un pixel bleu-ciel en mode 1, 4 couleurs (couleur n°2 par défaut), à la position X = 2, Y1 = 0 et Y2 = 4 ; voici comment devrait être affichés les pixels dans la mémoire :
Notre pixel bleu est toujours stocké dans l'octet 0 de la mémoire graphique. La table ci-dessous reprend les valeurs des masques logiques des quatre couleurs utilisables en mode 1. Le masque de la couleur 0 pour le pixel 0, le masque de la couleur 1 pour le pixel 0, le masque de la couleur 2 pour le pixel 0, etc. (Les masques de la table sont les masques par défaut du pixel 0)
Pour notre cas la valeur du masque logique pour coder la couleur bleue-ciel devra être décalée de deux bits vers la droite car le pixel concerné est le pixel 2, sa valeur sera %00100000. Ce masque sera lui aussi identique pour tous les pixels à afficher sachant que X ne va pas varier.
Table des masques logiques pour le mode 1 (4 couleurs)
Dans notre dernier exemple nous allons traiter l'affichage d'un pixel jaune en mode 2, 2 couleurs (couleur n°1 par défaut), à la position X = 6, Y1 = 0 et Y2 = 4 ; voici comment devrait être affichés les pixels dans la mémoire :
Table des masques logiques pour le mode 2 (2 couleurs)
Dans notre dernier exemple notre pixel est toujours stocké dans l'octet 0 de la mémoire graphique. La table propose les valeurs des masques logiques des deux couleurs utilisées en mode 2. Le masque de la couleur 1 pour le pixel 6 est égal au masque de la couleur 1 mais décalé de 6 bits vers la droite, ce qui donne %00000010.
Maintenant que vous avez compris comment trouver les masques logiques utilisés pour coder les couleurs en fonction des numéros des pixels, nous allons voir comment trouver les masques qui permettent de préserver les données des pixels voisins ; nous en avons déjà parlé plusieurs fois mais nous allons regardez un exemple concret pour chaque mode vidéo. (Voir l'affichage d'un pixel dans les différents modes graphiques).
En mode 0, 16 couleurs : | |
En mode 4, 4 couleurs : | |
En mode 2, 2 couleurs : | |
Je suis certain que vous avez tout pigé !
Regardons l'algorithme complet :
Affichage d'une ligne verticale de Y1 à Y2 à la position X avec la couleur c
On a :
|
Regardons maintenant en détail l'affichage d'une ligne verticale en mode 1, 4 couleurs :
1 : Calculer l'adresse du premier point; Adresse du premier pixel
Nous n'avons plus besoin de détailler ce calcul !
2 : Calculer les masques logiques de notre premier point en fonction de sa position
Ici IX contient l'adresse de base de la table contenant les valeurs entrelacées des données des couleurs. DE contient le numéro de la couleur à afficher, on ajoute donc DE à IX de manière à tomber sur le masque de la couleur courante. Rappelons que cette table de valeurs contient les masques des couleurs pour le premier pixel d'une série, il faut donc décaler ces valeurs de n bits vers le bas (vers la droite) en fonction de la position du pixel que vous voulez afficher ; exemple pour un pixel à la position 1, on décalerait les masques des couleurs de un bit vers le bas, pour un pixel à la position 2 deux bits, etc…
La valeur de X se trouve dans A, le fait de ne préserver que les trois derniers bits permet d'obtenir le numéro du pixel à afficher et donc de savoir de combien de bits il va falloir décaler le masque logique associé à la couleur. Pour le décalage de bits nous utilisons la fonction MATH_SRL_8 qui permet d'effectuer des décalages sur des nombre codés sur 8 bits.
Après avoir décalé nos masques, il nous reste qu'à inverser les bits du masque qui permet la préservation des données du pixel voisin. Rappelons que le masque permettant la préservation des données des pixels voisins correspond aux bits à 1 de ces mêmes pixels.
3 : Calculer le nombre de points à traiter
Il n'y a pas grand-chose à dire sur cette partie de code. Pour calculer le nombre de points à traiter il suffit d'effectuer l'opération Y2 – Y1, sachant que l'on affiche systématiquement le premier pixel.
4 : Affichage de notre point
Sachant que HL contient l'adresse vidéo de votre écran (sauf si vous avez redéfini l'adresse graphique), nous récupérons l'octet à l'adresse (HL) et nous appliquons nos masques logiques ; D contient le masque de préservation, E le masque associé aux données entrelacées de la couleur courante.
5 : Calcul de la position du point suivant en fonction de la série de lignes, courante
C contient dy, un OU logique nous permet de savoir si C = 0.
B contient le nombre de ligne restant à traiter dans la série de lignes, courante.
Lorsque toutes les lignes de la série de lignes courante ont été traitées, nous repositionnons l'adresse graphique contenue dans HL en fonction de la série de lignes, suivante.
LD SP, &800 ; Passage à la ligne suivante (HL = HL + &800)
ADD HL, SP ;
DEC C ; Décrémentation de C qui contient le nombre de points à
; traiter
JP _GRAPH_LINE_V_4_2
_GRAPH_LINE_V_4_END
Pour terminer, lorsque nous voulons passe à la ligne suivante, nous ajoutons à l'adresse graphique de base, l'offset &800. DEC C permet de décrémenter le nombre de points à traiter.
Rappels:
5.3 Affichage d'une ligne verticale en mode 0 (16 couleurs, 2 pixels par octet)
;********************************************************
; Affichage d'une ligne verticale en mode 0 (16 couleurs)
;
; En entrée
; D <=> Y1
; E <=> Y2
; BC <=> X
; A <=> Couleur de la ligne
; En sortie
; néant
; Registres affectés
; aucun
; Remarques
;
; GRAPH_LINE_V_16:
PUSH AF ; Sauvegarde des registres
PUSH BC ;
PUSH DE ; AF,BC,DE,HL,IX
PUSH HL ;
PUSH IX ;
; Adresse du premier pixel
;
; En entrée
; BC <=> X
; A <=> Y1
; En sortie
; HL <=> Adresse du point
; PUSH DE
; On préserve Y1 et Y2
LD E, A
LD A, D ; Y1 dans A
CALL GRAPH_ADR_OF_PIXEL
LD A, C ; La partie basse de X dans A
POP BC ; Restauration de Y1 et Y2 dans BC
; Préparation des masques logiques en fonction du n° de la couleur
LD D, 0
LD IX, DEF_GRAPH_TABLE_PIXEL_16 ; Référence à la table des masques 16 couleurs
ADD IX, DE
LD E, (IX) ; Masque logique associé à la couleur entrelacée dans B
LD D, 85 ; Masque logique permettant de préserver le pixel voisin
; dans D (voir affichage d'un pixel en mode 0)
; Vérification si le premier pixel est pair ou impair
; Dans le cas où le pixel est impair, on décale le masque associé
; à la couleur entrelacée, d'un bit vers le bas
; et l'on décale le masque logique permettant de préserver le pixel voisin
; d'un bit vers le haut
AND 1
OR A
JP z, _GRAPH_LINE_V_16_1
SRL E
SLA D
_GRAPH_LINE_V_16_1: ; Calcul du nombre de points à traiter
;
; En entrée
; B <=> Y1
; C <=> Y2
; LD A, C
SUB B
LD C, A ; Le nombre de points à traiter dans C
; Calcul du nombre de lignes restant à traiter dans la série courante
; (Une série = 8 lignes)
LD A, B ; B = Y1 AND 7
; On ne garde que les 3 derniers bits
LD B, A ;
LD A, 8 ; Le nombre de lignes restantes à traiter dans la série courante
SUB B ; = 8 - le nombre de lignes déjà traitées
LD B, A ; Le nombre de lignes restant à traiter dans B
; Valeur par défaut pour l'incrémentation de l'adresse mémoire contenue dans HL
; &800 <=> Permet de passer à la ligne suivante
; &50 <=> Permet de passer à la série de lignes suivante
; &4000 <=> Permet d'ajuster HL en fonction d'une série complète de lignes
; = [nombre de lignes dans une série] * &800 ; &3FB0 <=> = Espace d'adressage d'une série complète de lignes – Offset
; pour la série de lignes suivantes ; = &4000 - &50
; => HL = HL - &4000 + &50
;
LD IX, 0 ; Sauvegarde de l'adresse de la pile
ADD IX, SP ;
_GRAPH_LINE_V_16_2 ; Affichage du pixel
LD A, (HL)
AND D ; Préservation des données du pixel voisin
OR E ; Affichage du nouveau pixel
LD (HL), A ; Test pour voir si tous les points ont été traités
LD A, C
OR A
JP z, _GRAPH_LINE_V_16_END ; Vérification si l'on a atteint une fin de série de lignes
; (série toutes les 8 lignes)
DJNZ _GRAPH_LINE_V_16_3
LD SP, &3FB0 ; &3FB0 = &4000 - &50 = Espace d'adressage d'une série complète
; de lignes - Offset pour la série de lignes suivantes
SBC HL, SP ; => HL = HL - &4000 + &50
;
LD B, 8 ; On va traiter une nouvelle série de 8 lignes
_GRAPH_LINE_V_16_3
LD SP, &800 ; Passage à la ligne suivante (HL = HL + &800)
ADD HL, SP ;
DEC C ; Décrémentation de C qui contient le nombre de points à traiter
JP _GRAPH_LINE_V_16_2
_GRAPH_LINE_V_16_END LD SP, IX ; Restauration de l'adresse de la pile
POP IX ; Restauration des registres
POP HL ;
POP DE ; AF,BC,DE,HL,IX
POP BC ;
POP AF ;
RET
5.4 Affichage d'une ligne verticale en mode 1 (4 couleurs, 4 pixels par octet)
GRAPH_LINE_V_4:
PUSH AF ; Sauvegarde des registres
PUSH BC ;
PUSH DE ; AF,BC,DE,HL,IX
PUSH HL ;
PUSH IX ;
; Adresse du premier pixel
;
; En entrée
; BC <=> X
; A <=> Y1
; En sortie
; HL <=> Adresse du point
;
PUSH DE ; On préserve Y1 et Y2
LD E, A
LD A, D ; Y1 dans A
CALL GRAPH_ADR_OF_PIXEL
LD A, C ; La partie basse de X dans A
POP BC ; Restauration de Y1 et Y2 dans BC
; Préparation des masques logiques en fonction du n° de la couleur
LD D, 0
LD IX, DEF_GRAPH_TABLE_PIXEL_4 ; Référence à la table des masques 4 couleurs
ADD IX, DE
LD E, (IX) ; Masque logique associé à la couleur entrelacée dans B
; X se trouve actuellement dans A, on ne préserve que les 2 premiers bits
; de manière à savoir de combien de bits
; doivent être déclalés pour obtenir les masques logiques
AND 3
; Décalages des masques vers le bas en fonction du n° du pixel concerné
;
; Paramètres de la fonction MATH_SRL_8
; En entrée
; E <=> Entier de 8 bits
; A <=> Nombre de bits à décaler
; En sortie
; E <=> Résultat du décalage des n bits
;
; A et E sont déjà chargés avec leur valeur respective
CALL MATH_SRL_8 ; Décalage du masque de la couleur entrelacée, associée
; à la couleur du pixel
; En sortie E contient le masque de la valeur entrelacée
; associé à la couleur du pixel
LD D, E ; Sauvegarde de E
; A est déjà chargé avec le nombre de décalages
LD E, 136 ; Valeur par défaut du masque logique qui va permettre de préserver
; le pixel voisin, en Mode 1 = 136 <=> %10001000
CALL MATH_SRL_8 ; Décalage du masque négatif qui permet de préserver le pixel voisin
; Inversion du masque logique négatif de manière à préserver les données
; du pixel voisin
LD A, E
CPL
LD E, D ; Restauration de E qui doit contenir le masque entrelacé
; lié à la couleur du pixel
LD D, A ; D va contenir le masque logique permettant la préservation
; du pixel voisin
_GRAPH_LINE_V_4_1:
; Calcul du nombre de points à traiter
;
; En entrée
; B <=> Y1
; C <=> Y2
;
LD A, C
SUB B
LD C, A ; Le nombre de points à traiter dans C
; Calcul du nombre de lignes restant à traiter dans la série courante
; (Une série = 8 lignes)
LD A, B ; B = Y1
AND 7 ; On ne garde que les 3 derniers bits
LD B, A ;
LD A, 8 ; Le nombre de lignes restantes à traiter dans la série courante
SUB B ; = 8 - le nombre de lignes déjà traitées
LD B, A ; Le nombre de lignes restant à traiter dans B
; Valeur par défaut pour l'incrémentation de l'adresse mémoire contenue dans HL
; &800 <=> Permet de passer à la ligne suivante
; &50 <=> Permet de passer à la série de lignes suivante
; &4000 <=> Permet d'ajuster HL en fonction d'une série complète de lignes
; = [nombre de lignes dans une série] * &800
; &3FB0 <=> = Espace d'adressage d'une série complète de lignes – Offset
; pour la série de lignes suivantes
; = &4000 - &50
; => HL = HL - &4000 + &50
;
LD IX, 0 ; Sauvegarde de l'adresse de la pile
ADD IX, SP ;
_GRAPH_LINE_V_4_2
; Affichage du pixel
LD A, (HL)
AND D ; Préservation des données du pixel voisin
OR E ; Affichage du nouveau pixel
LD (HL), A
; Test pour voir si tous les points ont été traités
LD A, C
OR A
JP z, _GRAPH_LINE_V_4_END
; Vérification si l'on a atteint une fin de série de lignes
; (série toutes les 8 lignes)
DJNZ _GRAPH_LINE_V_4_3
LD SP, &3FB0 ; &3FB0 = &4000 - &50 = Espace d'adressage d'une série complète
; de lignes - Offset pour la série de lignes suivantes
SBC HL, SP ; => HL = HL - &4000 + &50
;
LD B, 8 ; On va traiter une nouvelle série de 8 lignes
_GRAPH_LINE_V_4_3
LD SP, &800 ; Passage à la ligne suivante (HL = HL + &800)
ADD HL, SP ;
DEC C ; Décrémentation de C qui contient le nombre de points à traiter
JP _GRAPH_LINE_V_4_2
_GRAPH_LINE_V_4_END
LD SP, IX ; Restauration de l'adresse de la pile
POP IX ; Restauration des registres
POP HL ;
POP DE ; AF,BC,DE,HL,IX
POP BC ;
POP AF ;
RET
5.5 Affichage d'une ligne verticale en mode 2 (2 couleurs, 8 pixels par octet)
;********************************************************
; Affichage d'une ligne verticale en mode 2 (2 couleurs)
;
; En entrée
; D <=> Y1
; E <=> Y2
; BC <=> X
; A <=> Couleur de la ligne
; En sortie
; néant
; Registres affectés
; aucun
; Remarques
;
;
GRAPH_LINE_V_2:
PUSH AF ; Sauvegarde des registres
PUSH BC ;
PUSH DE ; AF,BC,DE,HL,IX
PUSH HL ;
PUSH IX ;
; Adresse du premier pixel
;
; En entrée
; BC <=> X
; A <=> Y1
; En sortie
; HL <=> Adresse du point
;
PUSH DE ; On préserve Y1 et Y2
LD E, A ; La couleur dans E
LD A, D ; Y1 dans A
CALL GRAPH_ADR_OF_PIXEL
LD A, C ; La partie basse de X dans A
POP BC ; Restauration de Y1 et Y2 dans BC
; Préparation des masques logiques en fonction du n° de la couleur
LD D, 0
LD IX, DEF_GRAPH_TABLE_PIXEL_2 ; Référence à la table des masques 4 couleurs
ADD IX, DE
LD E, (IX) ; Masque logique associé à la couleur entrelacée dans B
; X se trouve actuellement dans A, on ne préserve que les 3 premiers bits de manière
; à savoir de combien de bits
; doivent être déclalés les masques logiques
AND 7
; Décalages des masques vers le bas en fonction du n° du pixel concerné
;
; Paramètres de la fonction MATH_SRL_8
; En entrée
; E <=> Entier de 8 bits
; A <=> Nombre de bits à décaler
; En sortie
; E <=> Résultat du décalage des n bits
;
; A et E sont déjà chargés avec leur valeur respective
CALL MATH_SRL_8 ; Décalage du masque de la couleur entrelacée,
; associée à la couleur du pixel
; En sortie E contient le masque de la valeur entrelacée
; associé à la couleur du pixel
LD D, E ; Sauvegarde de E
; A est déjà chargé avec le nombre de décalages
LD E, 128 ; X AND 7 = n° du pixel courant, A contient donc le nombre de bits
; à décaler.
; en Mode 2 le masque = 128 <=> %10001000
CALL MATH_SRL_8 ; Décalage du masque négatif qui permet de préserver le pixel voisin
; Inversion du masque logique négatif de manière à préserver les données
; du pixel voisin
LD A, E
CPL
LD E, D ; Restauration de E qui doit contenir le masque entrelacé lié
; à la couleur du pixel
LD D, A ; D va contenir le masque logique permettant la préservation
; du pixel voisin
_GRAPH_LINE_V_2_1:
; Calcul du nombre de points à traiter
;
; En entrée
; B <=> Y1
; C <=> Y2
;
LD A, C
SUB B
LD C, A ; Le nombre de points à traiter dans C
; Calcul du nombre de lignes restant à traiter dans la série courante
; (Une série = 8 lignes)
LD A, B ; B = Y1
AND 7 ; On ne garde que les 3 derniers bits
LD B, A ;
LD A, 8 ; Le nombre de lignes restantes à traiter dans la série courante
SUB B ; = 8 - le nombre de lignes déjà traitées
LD B, A ; Le nombre de lignes restant à traiter dans B
; Valeur par défaut pour l'incrémentation de l'adresse mémoire contenue dans HL
; &800 <=> Permet de passer à la ligne suivante
; &50 <=> Permet de passer à la série de lignes suivante
; &4000 <=> Permet d'ajuster HL en fonction d'une série complète de lignes
; = [nombre de lignes dans une série] * &800
; &3FB0 <=> = Espace d'adressage d'une série complète de lignes û Offset
; pour la série de lignes suivantes
; = &4000 - &50
; => HL = HL - &4000 + &50
;
LD IX, 0 ; Sauvegarde de l'adresse de la pile
ADD IX, SP ;
_GRAPH_LINE_V_2_2
; Affichage du pixel
LD A, (HL)
AND D ; Préservation des données du pixel voisin
OR E ; Affichage du nouveau pixel
LD (HL), A
; Test pour voir si tous les points ont été traités
LD A, C
OR A
JP z, _GRAPH_LINE_V_2_END
; Vérification si l'on a atteint une fin de série de lignes
; (série toutes les 8 lignes)
DJNZ _GRAPH_LINE_V_2_3
LD SP, &3FB0 ; &3FB0 = &4000 - &50 = Espace d'adressage d'une série complète
; de lignes - Offset pour la série de lignes suivantes
SBC HL, SP ; => HL = HL - &4000 + &50
;
LD B, 8 ; On va traiter une nouvelle série de 8 lignes
_GRAPH_LINE_V_2_3
LD SP, &800 ; Passage à la ligne suivante (HL = HL + &800)
ADD HL, SP ;
DEC C ; Décrémentation de C qui contient le nombre de points à traiter
JP _GRAPH_LINE_V_2_2
_GRAPH_LINE_V_2_END
LD SP, IX ; Restauration de l'adresse de la pile
POP IX ; Restauration des registres
POP HL ;
POP DE ; AF,BC,DE,HL,IX
POP BC ;
POP AF ;
RET
5.6 Affichage d'une ligne verticale indépendamment de mode graphique
;*****************************************************************
; Affichage d'une ligne verticale quelque soit le mode graphique
;
; En entrée
; D <=> Y1
; E <=> Y2
; BC <=> X
; A <=> Couleur de la ligne
; En sortie
; néant
; Registres affectés
; aucun
; Remarques
; - Les modes graphiques supportés sont les 0,1 et 2
;
GRAPH_LINE_V:
PUSH AF ; Sauvegarde de AF pour le retour
; Vérification du mode graphique en cours
LD A, CST_GRAPH_VIDEO_MODE
CP 2
JP z, _GRAPH_LINE_V_2
CP 1
JP z, _GRAPH_LINE_V_4
OR A
JP z, _GRAPH_LINE_V_16
; Retour si le mode graphique est <> 0,1 ou 2
POP AF
RET
; Affichage des pixels
;
; En entrée les fonctions possèdent les même paramètres
; D <=> Y1
; E <=> Y2
; BC <=> X
; A <=> Couleur de la ligne
_GRAPH_LINE_V_16
; Affichage de la ligne horizontale 16 couleurs
POP AF
CALL GRAPH_LINE_V_16
RET
_GRAPH_LINE_V_4
; Affichage de la ligne horizontale 4 couleurs
POP AF
CALL GRAPH_LINE_V_4
RET
_GRAPH_LINE_V_2
; Affichage de la ligne horizontale 2 couleurs
POP AF
CALL GRAPH_LINE_V_2
RET
GRAPH_LINE_V_END
Rien à signaller !
6 - Le tracé d'une ligne oblique
Cet algorithme est bien connu des développeurs, il a été inventé par Bresenham.
Voici ce que donnerait l'algorithme en Basic:
IF X1 < X2 THEN xincr = 1 ELSE xincr = -1
IF Y1 < Y2 THEN xincr = 1 ELSE yincr = -1
dx = ABS(X1 - X2)
dy = ABS(Y1 - Y2)
IF dy > dx THEN
Cumul = dy \ 2
FOR i = 1 TO dy
Y1 = Y1 + yincr
Cumul = cumul + dx
IF cumul >= dy THEN
Cumul = cumul - dy
X1 = X1 + xincr
END
CALL Pixel (X1, Y1, Couleur) ; Pixels suivants
NEXT i
END
ELSE
Cumul = dx \ 2
FOR i = 1 TO dx
X1 = X1 + xincr
Cumul = cumul + dy
IF cumul >= dx THEN
Cumul = cumul - dx
Y1 = Y1 + yincr
END
CALL Pixel (X1, Y1, Couleur) ; Pixels suivants
NEXT i
END
Regardons maintenant comment simplifier cet algorithme.
1 - Première simplification :
; | Les variables xincr et yincr permettent respectivement d'incrémenter X, Y si X1 et Y1 sont positifs ou dans le cas contraire, les décrémenter. Si nous nous arrangeons pour que X1 et Y1 soient toujours inférieurs à X2 et Y2, xincr et yincr seront toujours égaux à 1. |
2 – Seconde simplification :
Dans un second temps nous calculons les valeurs absolues de dx et dy. Dans le cas où X1 et Y1 sont toujours inférieurs à X2 et Y2, le calcul de dx et dy se réduit à :
dx = X2 – X1dy = Y2 – Y1
L'algorithme devient donc :
IF X2 < X1 THEN Echange X2 avec X1
IF Y2 < Y1 THEN Echange Y2 avec Y1
dx = X2 – X1
dy = Y2 – Y1
IF dy > dx THEN
Cumul = dy \ 2
FOR i = 1 TO dy
Y1 = Y1 + 1
Cumul = cumul + dx
IF cumul >= dy THEN
Cumul = cumul - dy
X1 = X1 + 1
END
CALL Pixel (X1, Y1, Couleur) ; Pixels suivants
NEXT i
END
ELSE
Cumul = dx \ 2
FOR i = 1 TO dx
X1 = X1 + 1
Cumul = cumul + dy
IF cumul >= dx THEN
Cumul = cumul - dx
Y1 = Y1 + 1
END
CALL Pixel (X1, Y1, Couleur) ; Pixels suivants
NEXT i
END
Ces simplifications ne sont pas très nombreuses mais permettent de simplifier le codage de la fonction en assembleur. Vous avez l'algorithme final, il nous reste plus qu'à le coder ! Pour éviter tout blabla… j'ai commenté le code à partir de l'algorithme pour qu'il soit plus compréhensible.
6.2 Affichage d'une ligne oblique en mode 0 (16 couleurs, 2 pixels par octet)
GRAPH_LINE_16:
PUSH AF ; Sauvegarde des registres
PUSH BC ;
PUSH HL ;
PUSH DE ;
PUSH IX ;
PUSH IY ;
; Création de la pile de travail
PUSH AF ; (SP) <=> Y1
; (SP + 1) <=> Couleur du point
; Calcul de la position de la pile de travail
PUSH HL
LD HL, 0
ADD HL, SP
INC HL
INC HL
PUSH HL
POP IY ; IY Contient l'adresse de basse de la pile de travail
POP HL
; Echange des valeurs si X1 > à X2 ou Y1 > Y2
;
; IF X1 > X2 THEN X1 <=> X2
; IF Y1 > Y2 THEN Y1 <=> Y2
;
;Y
LD A, D ; Comparaison Y1 avec Y2
CP E ; (D = Y1, E = Y2)
JP c, _GRAPH_LINE_16_1
LD D, E ; Echange Y1 avec Y2
LD E, A ;
_GRAPH_LINE_16_1
;X
OR A ; On efface Carry
PUSH HL
SBC HL, BC ; Si inférieur à 0 Carry = 1
JP nc, _GRAPH_LINE_16_2
LD H, B
LD L, C
POP BC
JP _GRAPH_LINE_16_3
_GRAPH_LINE_16_2
POP HL
_GRAPH_LINE_16_3
; -----------------------------------------------
; Affichage du premier pixel
;
; Paramètres en entrée de la procédure GRAPH_PIXEL
;
; En entrée
; BC <=> X sur 16 bits
; D <=> Y sur 8 bits
; E <=> Couleur du pixel sur 8 bits
;
; CALL Pixel (X1, Y1, Couleur) (BC, D, E)
;
PUSH DE
LD E, (IY + 1) ; Chargement de E avec la couleur sauvegarder à (SP + 3)
CALL GRAPH_PUT_PIXEL_16 ; Affichage du point en 16 couleurs
POP DE
; Stockage de X1 et de Y1
PUSH BC ; Récupération de X1 dans IX
POP IX ;
LD (IY), D ; Récupération de Y1 dans (IY)
; -----------------------------------------------
; Calcul de dx et de dy
;
; dx = X2 - X1
; dy = Y2 - Y1
;
; HL <=> dx
; BC <=> dy
; dx
OR A ; On efface Carry
SBC HL, BC
; dy
LD A, E
SUB D
LD B, 0
LD C, A
; IF dy > dx THEN
OR A ; On efface Carry
PUSH HL
SBC HL, BC
POP DE ; DE <=> dx
JP nc, _GRAPH_LINE_16_6
; -----------------------------------------------
; Tracé de la ligne (dy > dx)
;
; Rappel de l'affectation des registres
;
; HL <=> Cumul
; DE <=> dx
; BC <=> dy
; IX <=> X1
; A <=> Compteur dy
; (IY) <=> Y1
; Cumul = dy \ 2
; SRL -> RRA
LD H, B
LD A, C
SRL H
RRA
LD L, A ; HL <=> Cumul
; FOR i = dy TO 1
LD A, C
_GRAPH_LINE_16_4
; Y1 = Y1 + 1
INC (IY)
; Cumul = cumul + dx
ADD HL, DE
; IF cumul >= dy THEN
PUSH HL
SBC HL, BC
POP HL
JP c, _GRAPH_LINE_16_5
; Cumul = cumul - dy
SBC HL, BC
; X1 = X1 + 1
INC IX
; END
_GRAPH_LINE_16_5
; -----------------------------------------------
; Affichage du pixel courant
;
; Paramètres en entrée de la procédure GRAPH_PIXEL
;
; En entrée
; BC <=> X sur 16 bits
; D <=> Y sur 8 bits
; E <=> Couleur du pixel sur 8 bits
;
; CALL Pixel (X1, Y1, Couleur) (BC, D, E)
;
PUSH BC
PUSH DE
PUSH IX
POP BC
LD D, (IY) ; Chargement de D avec Y1 à (SP)
LD E, (IY + 1) ; Chargement de E avec la couleur sauvegarder à (SP + 1)
CALL GRAPH_PUT_PIXEL_16 ; Affichage du point en 16 couleurs
POP DE
POP BC
; NEXT i
DEC A
JP nz, _GRAPH_LINE_16_4
JP GRAPH_LINE_16_END
_GRAPH_LINE_16_6
; -----------------------------------------------
; Tracé de la ligne (dy <= dx)
;
; Rappel de l'affectation des registres
;
; HL <=> Cumul
; DE <=> dx
; BC <=> dy
; IX <=> X1
; (IY) <=> Y1
; Cumul = dx \ 2
; SRL -> RRA
LD H, D
LD A, E
SRL H
RRA
LD L, A ; HL <=> Cumul
; FOR i = dx TO 1
LD A, E
_GRAPH_LINE_16_7
; X1 = X1 + 1
INC IX
; Cumul = cumul + dy
ADD HL, BC
; IF cumul >= dx THEN
PUSH HL
SBC HL, BC
POP HL
JP c, _GRAPH_LINE_16_8
; Cumul = cumul - dx
SBC HL, DE
; Y1 = Y1 + 1
INC (IY)
; END
_GRAPH_LINE_16_8
; -----------------------------------------------
; Affichage du pixel courant
;
; Paramètres en entrée de la procédure GRAPH_PIXEL
;
; En entrée
; BC <=> X sur 16 bits
; D <=> Y sur 8 bits
; E <=> Couleur du pixel sur 8 bits
;
; CALL Pixel (X1, Y1, Couleur) (BC, D, E)
;
PUSH BC
PUSH DE
PUSH IX
POP BC
LD D, (IY) ; Chargement de D avec Y1 à (SP)
LD E, (IY + 1) ; Chargement de E avec la couleur sauvegarder à (SP + 1)
CALL GRAPH_PUT_PIXEL_16 ; Affichage du point en 16 couleurs
POP DE
POP BC
; NEXT i
DEC A
JP nz, _GRAPH_LINE_16_7
GRAPH_LINE_16_END
POP AF ; Destruction de la pile de travail
POP IY ; Restauration des registres
POP IX ;
POP DE ;
POP HL ;
POP BC ;
POP AF ;
RET
Si vous avez bien étudié sont fonctionnement vous comprendrez aisément le tracé de lignes 2 et 4 couleurs. Je vais vous présentez le codage de la fonction pour le tracé d'une ligne à quatre couleurs, pour le tracé d'une ligne en 2 couleurs c'est exactement la même chose. La seule différence en le tracé 16 couleurs et les tracés en 4 et 2 couleurs est l'utilisation d'une variable sur 16 bits qui permet de comptabiliser le nombre de points sur X et Y qui ont été traités.
6.3 Affichage d'une ligne oblique en mode 1 (4 couleurs, 4 pixels par octet)
LINE_4_COUNT DW 0
GRAPH_LINE_4:
PUSH AF
PUSH BC
PUSH HL
PUSH DE
PUSH IX
PUSH IY
; Création de la pile de travail
PUSH AF ; (SP) <=> Y1
; (SP + 1) <=> Couleur du point
; Calcul de la position de la pile de travail
PUSH HL
LD HL, 0
ADD HL, SP
INC HL
INC HL
PUSH HL
POP IY ; IY Contient l'adresse de basse de la pile de travail
POP HL
; Test des valeurs en entrée
; Echange des valeurs si X1 > à X2 ou Y1 > Y2
;
; IF X1 > X2 THEN X1 <=> X2
; IF Y1 > Y2 THEN Y1 <=> Y2
;
;Y
LD A, D ; Comparaison Y1 avec Y2
CP E ; (D = Y1, E = Y2)
JP c, _GRAPH_LINE_4_1
LD D, E ; Echange Y1 avec Y2
LD E, A ;
_GRAPH_LINE_4_1
;X
OR A ; On efface Carry
PUSH HL
SBC HL, BC ; Si inférieur à 0 Carry = 1
JP nc, _GRAPH_LINE_4_2
LD H, B
LD L, C
POP BC
JP _GRAPH_LINE_4_3
_GRAPH_LINE_4_2
POP HL
_GRAPH_LINE_4_3
; -----------------------------------------------
; Affichage du premier pixel
;
; Paramètres en entrée de la procédure GRAPH_PIXEL
;
; En entrée
; BC <=> X sur 16 bits
; D <=> Y sur 8 bits
; E <=> Couleur du pixel sur 8 bits
;
; CALL Pixel (X1, Y1, Couleur) (BC, D, E)
;
PUSH DE
LD E, (IY + 1) ; Chargement de E avec la couleur sauvegarder à (SP + 1)
CALL GRAPH_PUT_PIXEL_4 ; Affichage du point en 16 couleurs
POP DE
; Stockage de X1 et de Y1
PUSH BC ; Récupération de X1 dans IX
POP IX ;
LD (IY), D ; Récupération de Y1 dans (IY)
; -----------------------------------------------
; Calcul de dx et de dy
;
; dx = X2 - X1
; dy = Y2 - Y1
;
; HL <=> dx
; BC <=> dy
; dx
OR A ; On efface Carry
SBC HL, BC
; dy
LD A, E
SUB D
LD B, 0
LD C, A
; IF dy > dx THEN
OR A ; On efface Carry
PUSH HL
SBC HL, BC
POP DE ; DE <=> dx
JP nc, _GRAPH_LINE_4_6
; -----------------------------------------------
; Tracé de la ligne (dy > dx)
;
; Rappel de l'affectation des registres
;
; HL <=> Cumul
; DE <=> dx
; BC <=> dy
; IX <=> X1
; A <=> Compteur dy
; (IY)<=> Y1
; Cumul = dy \ 2
; SRL -> RRA
LD H, B
LD A, C
SRL H
RRA
LD L, A ; HL <=> Cumul
; FOR i = dy TO 1
LD (LINE_4_COUNT), BC
LD A, (IY)
_GRAPH_LINE_4_4
; Y1 = Y1 + 1
INC A
; Cumul = cumul + dx
ADD HL, DE
; IF cumul >= dy THEN
PUSH HL
SBC HL, BC
POP HL
JP c, _GRAPH_LINE_4_5
; Cumul = cumul - dy
OR A
SBC HL, BC
; X1 = X1 + 1
INC IX
; END
_GRAPH_LINE_4_5
; -----------------------------------------------
; Affichage du pixel courant
;
; Paramètres en entrée de la procédure GRAPH_PIXEL
;
; En entrée
; BC <=> X sur 16 bits
; D <=> Y sur 8 bits
; E <=> Couleur du pixel sur 8 bits
;
; CALL Pixel (X1, Y1, Couleur) (BC, D, E)
;
PUSH BC
PUSH DE
PUSH IX
POP BC
LD D, A
LD E, (IY + 1) ; Chargement de E avec la couleur sauvegarder à (SP + 1)
CALL GRAPH_PUT_PIXEL_4 ; Affichage du point en 16 couleurs
POP DE
; NEXT i
OR A
PUSH HL
LD BC, 1
LD HL, (LINE_4_COUNT)
SBC HL, BC
LD (LINE_4_COUNT), HL
POP HL
POP BC
JP nz, _GRAPH_LINE_4_4
JP GRAPH_LINE_4_END
_GRAPH_LINE_4_6
; -----------------------------------------------
; Tracé de la ligne (dy <= dx)
;
; Rappel de l'affectation des registres
;
; HL <=> Cumul
; DE <=> dx
; BC <=> dy
; IX <=> X1
; (IY)<=> Y1
; Cumul = dx \ 2
; SRL -> RRA
LD H, D
LD A, E
SRL H
RRA
LD L, A ; HL <=> Cumul
; FOR i = dx TO 1
LD (LINE_4_COUNT), DE
LD A, (IY)
_GRAPH_LINE_4_7
; X1 = X1 + 1
INC IX
; Cumul = cumul + dy
ADD HL, BC
; IF cumul >= dx THEN
PUSH HL
SBC HL, DE
POP HL
JP c, _GRAPH_LINE_4_8
; Cumul = cumul - dx
OR A
SBC HL, DE
; Y1 = Y1 + 1
INC A
; END
_GRAPH_LINE_4_8
; -----------------------------------------------
; Affichage du pixel courant
;
; Paramètres en entrée de la procédure GRAPH_PIXEL
;
; En entrée
; BC <=> X sur 16 bits
; D <=> Y sur 8 bits
; E <=> Couleur du pixel sur 8 bits
;
; CALL Pixel (X1, Y1, Couleur) (BC, D, E)
;
PUSH BC
PUSH DE
PUSH IX
POP BC
LD D, A ; Chargement de D avec Y1
LD E, (IY + 1) ; Chargement de E avec la couleur sauvegarder à (SP + 1)
CALL GRAPH_PUT_PIXEL_4 ; Affichage du point en 16 couleurs
POP DE
; NEXT i
OR A
LD BC, 1
PUSH HL
LD HL, (LINE_4_COUNT)
SBC HL, BC
LD (LINE_4_COUNT), HL
POP HL
POP BC
JP nz, _GRAPH_LINE_4_7
GRAPH_LINE_4_END
POP AF ; Destruction de la pile de travail
POP IY ; Restauration des registres
POP IX ;
POP DE ;
POP HL ;
POP BC ;
POP AF ;
RET
6.4 Affichage d'une ligne oblique quelque soit le mode graphique utilisé
GRAPH_LINE:
PUSH AF
; Vérification du mode graphique en cours
LD A, CST_GRAPH_VIDEO_MODE
CP 2
JP z, _GRAPH_LINE_2
CP 1
JP z, _GRAPH_LINE_4
OR A
JP z, _GRAPH_LINE_16
; Retour si le mode graphique est <> 0,1 ou 2
POP AF
RET
_GRAPH_LINE_16
; Affichage de la ligne 16 couleurs
POP AF
CALL GRAPH_LINE_16
RET
_GRAPH_LINE_4
; Affichage de la ligne 4 couleurs
POP AF
CALL GRAPH_LINE_4
RET
_GRAPH_LINE_2
; Affichage de la ligne 2 couleurs
POP AF
CALL GRAPH_LINE_2
RET
GRAPH_LINE_END
Pour conclure, si vous avez bien compris comment fonctionne les fonctions de traçage de lignes obliques, vous aller pouvoir facilement les optimiser en évitant un maximum d'appels à la fonction d'affichage des pixels en la codant directement et en évitant les PUSH et les POP…
Voilà ! J'espère que vous vous êtes bien amusé ?
Dans un prochain article nous étudierons l'affichage et la gestion des Sprites …
7.1 Opération de décalage de n bits vers la droite d'un entier 8 bits
MATH_SRL_8:
PUSH BC ; Sauvegarde de AF pour le retour
OR A ; Si B = 0 on sort
LD B, A ; Le nombre d'itération est transféré dans B pour le décompte
JP z, _MATH_SRL_8_END
_MATH_SRL_8_1
SRL E
DJNZ _MATH_SRL_8_1
_MATH_SRL_8_END
POP BC
RET
7.2 Opération de décalage de n bits vers la droite d'un entier 16 bits
MATH_SRL_16:
PUSH BC ; Sauvegarde de BC pour le retour
OR A ; Si B = 0 on sort
JP z, _MATH_SRL_16_END
LD B, A ; B va contenir le nombre de décalages
LD C, A ; Sauvegarde de A
LD A, E
_MATH_SRL_16_1
SRL D
RRA
DJNZ _MATH_SRL_16_1
LD E, A
_MATH_SRL_16_END
LD A, C ; Restauration de A
POP BC ; Restauration de BC
RET
7.3 Multiplication de deux entiers non signés de 16 par 8 bits, résultat sur 16 bits
MATH_MUL_SP_16x8:
EXX ; Sauvegarde des registres BC, DE, HL
EX AF, AF' ; Sauvegarde du registre AF
POP HL ; Récupération de l'adresse de retour
POP AF ; Récupération du multiplicateur 8 bits dans A
POP DE ; Récupération du multiplicande sur 16 bits
PUSH HL ; Sauvegarde de l'adresse de retour
LD HL, 0 ; Mise à blanc de HL pour le retour
; Vérification des valeurs en entrée
OR A ; Si A = 0 on sort
JP z, _MATH_MUL_SP_16x8_END
ADC HL, DE ; Si DE = 0 on sort
JP z, _MATH_MUL_SP_16x8_END
; Début du code lié à la multiplication
LD HL, 0
_MATH_MUL_SP_16x8_1
BIT 0, A ; Vérification si le bit 0 = 0, si oui
; on ne cumule pas le sous-produit courant
JP z, _MATH_MUL_SP_16x8_3
ADD HL, DE ; Cumule du sous-produit courant
_MATH_MUL_SP_16x8_3
EX HL, DE ; Décalage du sous-produit courant
ADD HL, HL ; de 1 bit vers le haut
EX HL, DE ;
SRL A ; Décalage du multiplicateur de 1 bit
; vers le bas
OR A ; Vérification que tous les bits du
; multiplicateur ont été traités
JP nz, _MATH_MUL_SP_16x8_1
_MATH_MUL_SP_16x8_END
PUSH HL ; Préservation du résultat
EXX ; Restauration des registres courants BC, DE, HL
EX AF, AF' ; Restauration de AF
POP DE ; Récupération du résultat dans DE
; (ou tout autre registre sur 16 bits)
RET
7.4 Division de deux entiers non signé de 16 par 8 bits, résultat sur 16 bits
MATH_DIV_SP_16x8:
EXX ; Préservation des registre BC, DE, HL
EX AF, AF' ; Préservation de AF
POP DE ; Récupération de l'adresse de retour
POP BC ; Récupération du diviseur dans B
; (partie haute à l'adresse [SP - 3])
POP HL ; Récupération du dividende
PUSH DE ; Sauvegarde de l'adresse de retour
; Test des valeurs en entrée
LD A, B
OR A
JP nz, _MATH_DIV_SP_16x8_1
LD H, A
LD L, A
JP _MATH_DIV_SP_16x8_END
_MATH_DIV_SP_16x8_1
LD C, B ; Stockage du diviseur dans C
LD B, 16 ; Les 16 bits de HL (le dividende) vont être traités
XOR A, A ; On efface A pour initialiser le sous-dividende
_MATH_DIV_SP_16x8_2
SLA L ; Décalage du bit le plus haut, du dividende dans Carry
RL H
RLA ; Récupération de Carry dans le sous-dividende (A)
; (création du sous dividende)
CP C ; Vérification si le sous-dividende est supérieur ou
; égal au diviseur. Si oui le quotient = + 1, sinon on
; traite le bit suivant, du dividende
JR c, _MATH_DIV_SP_16x8_3
INC HL ; Incrémentation du quotient (sachant que tous les bits
; de HL sont décalé vers le haut (la gauche), les bits
; venant du bas vont servir pour stocker le quotient
SUB C ; On soustrait le diviseur du dividende
_MATH_DIV_SP_16x8_3
DJNZ _MATH_DIV_SP_16x8_2 ; Décrémentation de B de manière à
; traiter les 16 bits du dividende
_MATH_DIV_SP_16x8_END
PUSH HL ; Sauvegarde du quotient sur la pile, le reste se trouve dans A
EXX ; Restauration des registres BC, DE, HL
LD D, A ; Sauvegarde du reste de l'opération dans A
EX AF, AF' ; Restauration de F
LD A, D ; A contient le reste de la division entière
POP DE ; Récupération du résultat dans DE
; (ou tout autre registre 16 bits)
RET
contact : vincentbouffigny.cpc@free.fr
web : http://ovi.org.free.fr - http://projets.infos.free.fr
Page précédente : Les opérations, la pile et les registres spécialisés par ANTIBUG |
|