l' algoritmo di Bresenham
GRAFICA INCREMENTALE
come si disegna un
segmento di retta su
un reticolo di pixel
senza prodotto ne'
divisione:
l' algoritmo di Bresenham
risale al 1961, pubblicato nel 1965
Introduzione alla Grafica Digitale - tracciare un segmento
schermo a raggi catodici, disegno di tipo vettoriale
(vedi Tektronix, history ...)
una linea era tracciata sullo schermo comandando
direttamente la posizione del pennello luminoso da una
posizione iniziale (x1,y1) ad una posizione finale (x2,y2),
il segmento P1--P2 e' un vettore (spazio 2D) ovvero una
coppia di punti orientata ovvero un punto + una direzione ed
una lunghezza:
P2(x1,y1)
P1(x1,y1)
Introduzione alla Grafica Digitale - tracciare un segmento
per il plotter o tracciatore meccanico si pone il problema di
come tracciare una linea con una penna comandata da
due motori incrementali passo passo (uno per il tamburo
porta carta l'altro per il carrello porta penna)
i motori sono essenzialmente digitali, capaci di far
avanzare la penna di un passo (fisso) nel senso delle x o
nel senso delle y, o in entrambi (diagonale esatta)
carrello
porta penna
tamburo porta
carrello
problema su un plotter o tracciatore meccanico
(i plotter sono ancora in uso ... anni 61 .. 08)
di come tracciare una linea con una penna (varie tecnologie,
dalla biro, ai pennini in china, al pennello, all;ink-jet, alla carta
termica ecc)
comandata da due motori incrementali (digitali!)
che fanno muovere la penna di un passo fisso
nel senso delle x, delle y, o entrambi (diagonale esatta);
sulla pagina seguente un "home made plotter"
(un plotter e il suo driver sono stati fatti da due studenti
del corso di Calcolatori elettronici 2 (Architettura di ..)
per hobby e poi presentati come tesina per l'esame;
come si vede dalla foto, era fatto
con il gioco "FisherTechnik"
Introduzione alla Grafica Digitale - tracciare un segmento
il problema di come tracciare una linea con una penna che
puo' avanzare di un passo (fisso) nel senso delle x o nel
senso delle y, o in entrambi (diagonale esatta)
si ripropone comunque sugli schermi a reticolo dagli stessi
anni 60 in poi, (primi schermi grafici 1960)
schermi grafici a reticolo a basso costo (300 x 200 circa (*)
(Apple2,1977,1300$ con 4kbyte),
schermi a tubo vettoriali a immagine persistente a fosfori
verdi (Tektronix anni 70-80),
"super" schermi di oggi (2007) (3840x2400)
--------------------------------
(*)
280x192 a 4 colori
Introduzione alla Grafica Digitale - tracciare un segmento
(*)
(*)
(da Wikipedia)
IBM 1401 calcolatore commerciale usato spesso come controller per plotter,
Introduzione alla Grafica Digitale - tracciare un segmento
Una linea disegnata con un plotter oppure su uno schermo
a reticolo (sul monitor posso disegnare un "punto" di
dimensione fissa = pixel ) diventa una linea spezzata:
richiesta la
linea blu
orizzontale dx
diagonale dx,dy
verticale dy
con il plotter devo disegnare una linea approssimante,
perche' la penna puo' avanzare solo di un passo fisso:
nel senso delle x (orizzontale) o
nel senso delle y (verticale) o
in entrambi i sensi - diagonale esatta
Introduzione alla Grafica Digitale - tracciare un segmento
Una linea su uno schermo a reticolo (posso disegnare un
"punto" di dimensione fissa = pixel ) diventa una
successione di pixel come in figura (linea spezzata) :
coordinate
schermo:
xey
sono interi
Introduzione alla Grafica Digitale - tracciare un segmento
segmento da un punto P1(x1,y1) - a un punto P2(x2,y2)
nota: punto dato in coordinate schermo,
con due interi x1,y1
** x valore da xmin (in genere zero) a xmax (larghezza
dello schermo in pixel),
** y da ymin a ymax
(y=0 bordo in basso / in alto dello schermo)
non posso tracciare il segmento geometrico astratto, devo
tracciare una sequenza di pixel con posizioni x1,y1, ...
x2,y2, dove
si incrementa x ad ogni passo se l' inclinazione del
segmento e' minore o uguale a 450 ,
oppure incremento y se il segmento ha inclinazione
maggiore di 45 0
Introduzione alla Grafica Digitale - tracciare un segmento
segmento da un punto P1(x1,y1) - a un punto P2(x2,y2)
punto dato in coordinate schermo, con due interi x1,y1
** x va da xmin a xmax
(da zero a larghezza schermo in pixel),
** y da ymin a ymax
(da 0 a altezza dello schermo)
devo tracciare una sequenza di pixel
con posizioni x1,y1, xa,ya, xb,yb, ... x2,y2, dove
se l' inclinazione del segmento e' minore o uguale a 450
e quindi vi sono piu' xk diversi che yk diversi, e si procede
incrementando x ad ogni passo
oppure se il segmento ha inclinazione maggiore di 45 0 e
allora vi saranno piu' y distinte che x distinte, e si procede
incrementando y ad ogni passo
Introduzione alla Grafica Digitale - tracciare un segmento
quindi: y = m * x + b
per disegnare un segmento
da P1 a P2 devo controllare
l'inclinazione m :
se m<=1, procedo a passi
fissi (un pixel) lungo le x la y viene incrementata di un
passo dy minore di un pixel,
e quindi arrotondata (fig. a
destra sopra)
---------------------------------------------------------------
per una retta con m>1 vado
a passo fisso lungo le y, e la
x sara' arrotondata, come
nella figura a destra sotto,
m<=1
y
x
x= k
y
k+5
k+4
k+3
k+2
k+1
y=k
k+2 k+4
k+1 k+3
k+7
m>1
x
Introduzione alla Grafica Digitale - tracciare un segmento
// versione semplice 1,
(*) nota: ad ogni passo
// con aritmetica float
c'e' da fare un prodotto
float a,b,x,y;
e una somma
..dx=xend-xstart;
dy=yend-ystart;..
if( fabs(dx) >= fabs(dy) ){
y
m = dy / dx;
// m<=1
// dove y= m*x+b; dy= m*dx
b= ystart - m * xstart;
x
x= xstart; // si parte da qui
k
k+2 k+4
do{ y = m*x+b; // (*) nota
k+7
k+1 k+3
putpix( (int)x, (int)y );
x=x+1; // sempre unitario!
} while( x<= xend );
} else ... pagina seguente
Introduzione alla Grafica Digitale - tracciare un segmento
//vers.semplice 1, aritm.float
float a,b,x,y;
..dx=xend-xstart;
dy=yend-ystart;
...
else { // fabs(dy) > fabs(dx)
mre = dx/dy; // mre < 1
// dove x= mre*y+b; dy= m*dx
b= xstart - mre * ystart;
y= ystart; // si parte da qui
do{ x = mre * y + b;
putpix( (int)x, (int)y );
y=y+1; //sempre unitario!
} while( y<= yend );
} // if ( fabs(dx)..
y
k+5
k+4
k+3
k+2
k+1
k
x
Introduzione alla Grafica Digitale - tracciare un segmento
//versione semplice 2, aritmetica float
float x=xstart, y=ystart, ddx,ddy,fsteps;
dx=xend-xstart; dy=yend-ystart;
if( fabs(dx) > fabs(dy) ){
// m<1
steps=fabs(dx); fsteps=(float)steps;
ddx= dx/fsteps; ddy= dy/fsteps;
putpix(round(x),round(y));
for(k=0; k<steps;k++) {
y
x=x+ddx; y=y+ddy;
putpix(round(x),round(y));
} //
} else ...
nota: ad ogni passo
c'e' da fare una
somma, ma..
x
k
k+2 k+4
k+1 k+3
k+7
Introduzione alla Grafica Digitale - tracciare un segmento
//vers. semplice 2, in float, parte else ...
float x=xstart, y=ystart, ddx,ddy,fsteps;
dx=xend-xstart; dy=yend-ystart; ...
} else {
// ( fabs(dx) < fabs(dy) ), m>1
steps=fabs(dy); // dx,dy interi
fsteps=(float)steps;
ddx= dx/fsteps; ddy= dy/fsteps;
putpix (round(x),round(y));
for(k=0; k<steps;k++) {
x=x+ddx; y=y+ddy;
putpix (round(x),round(y));
} //
} // if(fabs(dx)>fabs(dy)..else
y
k+5
k+4
k+3
k+2
k+1
k
x
tracciare un segmento versione semplice 2, aritmetica float ...
float x=xstart, y=ystart, ddx, ddy, fsteps;
dx=xend-xstart; dy=yend-ystart;
y
if( fabs(dx) > fabs(dy) ){ // m<1(m=dy/dx)
steps=fabs(dx); fsteps=(float)steps;
ddx= dx/fsteps; ddy= dy/fsteps;
putpix(round(x),round(y));
for(k=0; k<steps;k++) {
x=x+ddx; y=y+ddy;
x
putpix(round(x),round(y));
ad ogni passo c'e' da fare
} // for
solo una somma,
} else { // fabs(dx)<fabs(dy), m>1
steps=fabs(dy); fsteps=(float)steps; ma ... problema:
accumulo err float in
ddx= dx/fsteps;
ddy= dy/fsteps;
x=x+ddx; e y=y+ddy;
putpix (round(x),round(y));
con segmento lungo non
for(k=0; k<steps;k++) {
arrivo a xend, yend
x=x+ddx; y=y+ddy;
putpix (round(x),round(y));
inoltre (1962!)
} // for
l' aritmetica float
} // if ..else
richiede piu' tempo ...
tracciare un segmento: algoritmo di Bresenham
algoritmo di Bresenham per tracciare un segmento con
solo uso di aritmetica intera (Bresenham, J.E., "Algorithm for
Computer Control of a Digital plotter", IBM Systems Journal,
4(1), pp.25-30, 1965) esso si basa su un criterio di scelta del
pixel da tracciare che usa solo interi:
consideriamo il caso con
m<1, cioe' xstart<xend
(ottante direzione ENE,
come in figura a destra)
per il segmento in figura, al
passo k+1 dobbiamo
decidere se tracciare il pixel
(x+1,y) oppure (x+1,y+1)
y
y+1
y
x
k
k+1
k+2 k+3
k+4
tracciare un segmento: algoritmo di Bresenham
tracciato il pixel al passo k, dobbiamo decidere dove
mettere il pixel al passo k+1;
e a tal fine si ricorda
l' equazione della retta che passa per P1 e P2:
y=m*x+b,
con yst=m*xst+b, e yend=m*xend+b;
per xk : yk = m*xk + b, e, ricordando che xk+1 = xk + 1
per xk+1 : y = m*xk+1 + b = m * (xk+1) +b ... y non e' int !
al passo k+1 si decide tra il
dupper
y
pixel (x k+1 ,yk ) e (x k+1 ,yk+1 )
a seconda se y(xk+1) e' piu'
y k+1
vicino a yk+1 oppure a yk :
yk
dupp=yk+1-y =(yk+1)-m*(xk+1)-b
dlow = y - yk = m * (xk+1)+b - yk
dlo -dup =2*m*(xk+1)+2b-yk-yk-1
P1
P2
y
x
Xk
X k+1
dlower
tracciare un segmento: algoritmo di Bresenham
linea P1(xst,yst)-P2(xend,yend); per xk e' yk = m*xk +b, per xk+1
ho: y = m*xk+1+b = m*(xk+1)+b;
devo decidere tra (x k+1 ,yk ) e
(x k+1 ,yk+1 ) , cioe' (y non e'intero!) tra yk+1 e yk
dupp=yk+1-y =(yk+1) - m*(xk+1) - b
dlow = y - yk = m * (xk+1)+b - yk
dlo -dup=2*m*(xk+1)+2b-yk-yk-1
come criterio di decisione si
y
usa pk = dx* ( dlo -dup ) con
dx=xend-xstart e m = dy/dx
y k+1
nota che pk e' un intero, e
yk
nota il segno di dlo -dup : se
dlo<dup allora pk e' negativo,
P1
e si sceglie il pixel sotto, se
dlo>dup allora pk e' positivo,
e si sceglie il pixel sopra !
dupper
P2
y
x
Xk
X k+1
dlower
tracciare un segmento: algoritmo di Bresenham
Bresenham: il calcolo di pk da pk-1 si puo' fare senza prodotti e solo con int !!!
yst=m*xst+b; yend=m*xend+b; per xk e' yk = m*xk +b,
per xk+1 e'
yk+1 =m*xk+1 +b = m*(xk+1)+b ;
dupp = yk+1-y = (yk+1) - m*(xk+1)-b ;
dlow = y - yk = m *(xk+1)+b - yk;
dlow -dupp = 2*m*(xk+1)+2b-yk- yk-1;
il criterio di decisione pk per scegliere yk+1 o yk+1 e' dato dal pk-1 :
pk = dx* ( dlow -dupp ), dove se
dupper
dx=xend-xstart e m = dy/dx :
y
P2
pk= dx*(2*m*(xk+1)+2b-yk- yk-1)
pk= 2*dy*(xk+1) -2*dx*yk +c
y k+1
y
(c = 2*dy+dx*(2*b-1) sara' eliminato
yk
nel calcolo di pk+1 da pk )
x
P1
pk+1=2*dy*(xk+1+1) -2*dx*yk+1 +c
X k X k+1
pk+1-pk=2*dy*(xk+1-xk)-2*dx*(yk+1-yk)
pk+1 = pk + 2*dy*(xk+1-xk) -2*dx*(yk+1 -yk )
dlower
dove
xk+1-xk e' 1
e dove
z = yk+1-yk e' 0 se pk < 0, altrimenti z = 1:
pk+1 = pk + 2*dy - 2*dx*z ;
e con il valore iniziale p0 = 2*dy - dx;
da cui l'algoritmo di B.->
tracciare un segmento: algoritmo di Bresenham
ricordiamo: pk+1 = pk + 2*dy -2*dx * z,
dove se pk e'negativo, allora z = 0 altrimenti 1, e p0
= 2*dy - dx; ==>> da cui algoritmo (versione 1)
dati (x1,y1) e (x2,y2), con x1<x2; poni x=x1,y=y1;
traccia pixel (x,y); calcola dx=(x2-x1); dy=(y2-y1);
p=2*dy - dx; (val. iniz. del parametro di decisione)
ripeti dx-1 volte {
dupper
y
se p<0 allora (z=0, e )
il prossimo pixel e' (xk+1 ,yk )
y k+1
e il prossimo p = p+2*dy,
altrimenti (z=1, e )
yk
il pixel e' (x k+1 ,yk+1 ) e
P1
X k X k+1
il prossimo p = p+2*dy-2*dx;
incrementa x di 1
dlower
} ripeti
P2
y
x
tracciare un segmento: algoritmo di Bresenham
da cui infine:
void BresenhamX(int x1, int y1, int x2, int y2) {
int x=x1, y=y1; // only octant ENE, m<=1, dx>dy
int dx = x2 - x1, dy = y2 - y1;
int Two_dy = 2*dy, TwoDyMinusDx=2*(dy-dx);
int error = Two_dy - dx;
// prima era pk
putpix(x,y); // *** starting point
while (x<x2) {
x++; // here only octant ENE, m<=1;
if (error < 0) error = error + Two_dy;
else { // error > 0, go up one y:
y = y + 1; error = error + TwoDyMinusDx;
} // if error
putpix(x,y); // *** on new x,y
} // while x
nota: nel ciclo solo un test
e due somme di interi
} // Bresenham
tracciare un segmento: algoritmo di Bresenham
o, in forma leggermente diversa:
struct tPunto{ int x; int y; }; ...
void bresenham( tPuntoI a, tPuntoI b){
// deve essere a.x < b.x e 0 < H/W < 1,
// quadrante x>0,y>0, m<1
int y = a.y, W = b.x-a.x, H = b.y-a.y; //dx,dy
int E = 2*H-W; // criterio errore
for(int x=a.x; x<=b.x; x++) { // ciclo pixel
setPixel(x,y); // ad ogni passo x++, per y:
if(E<0) E += 2*H; // per il prossimo pixel
else { y++; // si sale di un pixel
E += 2*( H-W);
} // if
} // for x
} // bresenham
Introduzione alla Grafica Digitale - tracciare un segmento
la versione completa dell'algoritmo di Bresenham per
tracciare una linea e' un po' piu' lunga, perche' deve
considerare i vari casi che possono presentarsi:
** caso di linea molto inclinata (m>1, ovvero dy>dx)
** caso di punto di inizio "invertito" rispetto il punto di fine,
ovvero: (m<1) xend<xstart, oppure (m>1) yend<ystart
il caso m>1 si risolve scambiando le x con le y,
il caso m<1, xstart>xend si risolve scambiando il punto di
inizio con il punto di fine;
riportiamo la versione completa, dall'esercizio
OGL...Bresenham
void LineBres(int xstart,int ystart,int xend,int yend) {
int i,tmp,x,y,dx,dy,Signdx,Signdy,Two_dx,Two_dy,Swap,error;
if(abs(xstart-xend)<2) { // vertical: y=ystart;
if (ystart < yend) dy=1; else dy=-1;
do { setPixel(xstart,y); y=y+dy;
} while( abs(yend-y)>1); return;
} // if verticale
if(xstart>xend){scamb(&xstart,&xend);scamb(&ystart,&yend);}
x=xstart; y=ystart; dx=xend-xstart; dy=yend-ystart;
Signdx = isign(dx); Signdy = isign(dy);
dx = abs(dx); dy = abs(dy);
if (dy>dx) { tmp=dx;dx=dy; dy=tmp; Swap=1; } else Swap = 0;
Two_dx = 2*dx; Two_dy = 2*dy; error = Two_dy - dx;
for (i=1; i<=dx; i++) { setPixel(x,y);
if (error > 0) {
if( Swap ) x = x + Signdx; else y = y + Signdy;
error = error - Two_dx;
}
if ( Swap ) y = y + Signdy; else x = x + Signdx;
error = error + Two_dy;
} // for
setPixel(x,y);
} // LineBres
traccia un segmento di linea retta
algoritmo di Bresenham, programma EGD_2A_D
Introduzione alla Grafica Digitale - tracciare un segmento
Le librerie
core (GL) lib,
GLU (GL Utility lib),
GLUT (GL utility Toolkit lib)
hanno definiti vari oggetti, tra cui le curve di Bezier (core), le
superfici quadriche (GLU), vari solidi (cono,cubo,sfera,
ottaedro, icosaedro, toro, la tea-pot,
ma non hanno il cerchio e l'ellisse, (che si ottengono come
casi particolari dagli oggetti sopramenzionati);
cerchio e ellisse si possono/devono tracciare con algoritmi
discreti DDA; non riportiamo lo sviluppo, (bibliogr) ; si veda
ad es. il OGL6B (algor.di Bresenham per cerchio)
nel caso del cerchio, si calcola un ottavo del cerchio, e
poi si tracciano le altre sette parti per simmetria:
3
// il primo punto :
setP(xc+dx, yc+dy); // 1
// a gli altri:
1
// 1) cambia segno
setP(xc-dx, yc+dy); // 3
setP(xc+dx, yc-dy); // 4
8 setP(xc-dx, yc-dy); // 2
5
7
2
4
// 2) per simmetria:
6 setP(xc+dy, yc+dx); // 8
setP(xc-dy, yc+dx); // 5
setP(xc+dy, yc-dx); // 6
setP(xc-dy, yc-dx); // 7
Bresenham per il cerchio vedi pagina seguente:
3
Bibliografia:
Hearn,D., Baker,M.P.,
"Computer Graphics with
OpenGL", Pearson
Prentice-Hall, 2004, 3.rd ed.,
par.3-5, pp.92-109
1
5
8
7
6
2
4
Bresenham,J.E., "Algorithm
for Computer Control of a
Digital Plotter", IBM
Systems Journal, 4(1),
pp.25-30, 1965
Bresenham,J.E., "A Linear
Algorithm for Incrementing
Digital Display of Circular
Arcs", Communication of the
ACM, 20(2), pp.1 1977
void DDACircle
(int xc,int yc, int radius) {
// vedi bibliografia per la
// derivazione del procedimento
int dx,dy; // circle point
int p = 1-radius;
dx=0; dy=radius;
// point at alfa=90degr, on top of circle
set8Pixel( xc,yc, dx, dy );
while( dx<dy ) {
dx++;
if(p<0)
p= p+ 2*dx +1;
else { dy--; p= p+ 2*( dx-dy ) +1;}
set8Pixel( xc,yc, dx, dy ); // pag seg.
} // while dx
} // DDACircle
void set8Pixel(int xc,int yc, int dx,int dy,
double r, double g, double b )
/* replicate 8 times symmetrically
in the 8 octants */
{
setPixel(xc+dx, yc+dy, r,g,b );
setPixel(xc-dx, yc+dy, r,g,b );
setPixel(xc+dx, yc-dy, r,g,b );
setPixel(xc-dx, yc-dy, r,g,b );
setPixel(xc+dy, yc+dx, r,g,b );
setPixel(xc-dy, yc+dx, r,g,b );
setPixel(xc+dy, yc-dx, r,g,b );
setPixel(xc-dy, yc-dx, r,g,b );
} /* set8Pixel */
cerchio disegnato
con l'algoritmo di
Bresenham
(programma
EGD_22_CIRCLE)
I programmi dimostrativi degli algoritmi di Bresenham riportati
nel secondo capitolo EGD_07_2D_2_3 usano "pixel"
grandi, ovvero rettangoli di una matrice di rettangoli:
/* the simulated 1 bit raster memory */
int RasterMem[RASTERxSIZE][RASTERySIZE];
...
void plotpix(int x,int y, td r, td g, td b ) {
RasterMem[x][y] = 1; glColor3f(r,g,b );
glRecti( PixelSizeX * x , PixelSizeY * y ,
PixelSizeX * (x+1), PixelSizeY * (y+1) );
} /* plotpix */
...
void Simple(int x1,int y1,int x2,int y2){
float a,b,x,y,dx,dy;..dy=y2-y1; dx=x2-x1;
a=dy/dx; b=y1-a*x1; /*y=a*x+b;->b=y-a*x;*/ x=x1;
do{ y = a*x+b;
plotpix( (int)x, (int)y, 1.0,1.0,1.0);
x=x+1;
} while( x<= x2 ); ...
Oggi l'algoritmo di Bresenham e' realizzato a
livello hardware, nell'unita' di processo grafica,
e viene ricordato per il suo valore storico
(si pensi che al tempo (1961) i calcolatori spesso non
avevano un'unita' aritmetica completa, ma sapevano solo
sommare due interi positivi - il prodotto era quindi
realizzato a software, con una procedura ad hoc, e
l'evitare il prodotto velocizzava l'algoritmo di molto .