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,¥d);} 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 .