Michener’s Algorithm An efficient scheme for drawing circles (and filling circular disks) on a raster graphics display Scan conversion • • • • • • Geometric objects possess implicit parameters Example: A circle has a ‘center’ and a ‘radius’ Its equation is: (x – xc)2 + (y - yc)2 = R2 x and y are from the continuum of real numbers CRT display is a discrete 2D array of ‘pixels’ So drawing a circle requires a conversion, from something continuous into something discrete • In computer graphics it’s called scan conversion • Imperfections: unavoidable, but to be minimized Graphics Animations • Fast drawing is essential for animations • Some guidelines for speed: – eliminate all redundant computations – prefer integer arithmetic to floating-point – prefer add and subtract to multiply or divide • Famous example: ‘Michener Algorithm’ • We can use it later with our ‘pong’ game Eight-fold symmetry (-x, y) (x, y) (-y, x) (y, x) (-y, -x) (y, -x) (-x, -y) (x, -y) Compute only one octant Subroutine: draw_octant_points Arguments: int x, y, xcent, ycent, color; draw_pixel( xcent + x, ycent + y, color ); draw_pixel( xcent + y, ycent + x, color ); draw_pixel( xcent - x, ycent + y, color ); draw_pixel( xcent - y, ycent + x, color ); draw_pixel( xcent + x, ycent - y, color ); draw_pixel( xcent + y, ycent - x, color ); draw_pixel( xcent - x, ycent - y, color ); draw_pixel( xcent - y, ycent - x, color ); The “best” pixel to draw? Blue pixel: too far from center (E>0) Red pixel: too near the center (E<0) Error-term: E = (x2 + y2) – R2 Decision at n-th stage Pn-1 An ? yn-1 Bn ? xn-1 xn Algorithm: compute sum = error( An ) + error( Bn ); If ( sum < 0 ) choose A n; otherwise choose B n. Formula: sum of error-terms • • • • Assume circle has radius R, center (0,0) error( An) = (xn-1+1)2 + (yn-1)2 – R2 error( Bn) = (xn-1+1)2 + (yn-1 – 1)2 – R2 sumn = 2(xn-1+1)2 + 2(yn-1)2 –2(yn-1) + 12R2 • Now, how is sumn different from sumn+1: – If An is chosen at the n-th step? – If Bn is chosen at the n-th step? Difference Equation • Observe that: sumn+1 – sumn = 4xn-1 + 6 + 2(yn2 – yn-12) – 2(yn – yn-1) • When An is selected at n-th stage: – we will have yn = yn-1 – thus: sumn+1 = sumn + 4xn-1 + 6 • When Bn is selected at n-th stage: – we will have yn = yn-1 - 1 – thus: sumn+1 = sumn + 4(xn-1 – yn-1) + 10 Algorithm initialization • We start with the point P0 = (x0,y0), where x0 = 0 and y0 = R • In this case: A1 = (1, R) and B1 = (1, R-1) • So the initial ‘sum-of-errors’ term will be: sum1 = error(A1) + error(B1) = [(12 + R2) – R2] + [(12 + R2–2R+1) – R2] = 3 – 2R Michener’s Algorithm int x = 0, y = R, sum = 3 – 2*R; while ( x <= y ) { draw_octant_points( x, y, xc, yc, color ); if ( sum < 0 ) sum += 4*x + 6; else { sum += 4*(x - y) + 10; --y; } ++x; } Reference • Francis S. Hill, jr., “Computer Graphics,” Macmillan (1990), pp. 433-435. • NOTE: Michener’s circle-drawing method owes its inspiration to a famous algorithm for efficient line-drawing, devised in 1965 by J. E. Bresenham (see the IBM Systems Journal, vol 4, pp. 305-311). Circle ‘fill’ also exploits symmetry (-x, y) (x, y) (-y, x) (y, x) (-y, -x) (y, -x) (-x, -y) (x, -y) Subroutine: draw_segments Arguments: int x, y, xc, yc, color; draw_horiz( xc – x, xc + x, yc + y, color ); draw_horiz( xc – x, xc + x, yc - y, color ); draw_horiz( xc – y, xc + y, yc + x, color ); draw_horiz( xc – y, xc + y, yc - x, color ); draw_horiz( int xlo, xhi, y, color ); Clipping to screen boundaries: If (( y < ymin )||( y > ymax )) return; if ( xlo < xmin ) xlo = xmin; if ( xhi > xmax ) xhi = xmax; Drawing the horizontal segment: for (x = xlo; x <= xhi; x++) draw_pixel( x, y, color ); Demo-program • • • • • Try the ‘michener.cpp’ demo It uses VESA graphics-mode 0x4101 Screen resolution is 640x480 Color depth is 8 bits-per-pixel (8bpp) SVGA’s Linear Frame Buffer is enabled In-Class Exercise • Modify the ‘michener.cpp’ demo: – Use the standard ‘rand()’ function – Draw lots of color-filled circles – Stop if user hits <ESCAPE> key • NOTE: For the latter feature, we need to discuss setting up the terminal keyboard so it uses a ‘non-canonical’ input-mode The ‘tty’ interface • • • • ‘tty’ is an acronyn for ‘TeleTYpe’ terminal Such devices have a keyboard and screen Behavior emulates technology from 1950s Usually a tty operates in ‘canonical’ mode: – Each user-keystroke is ‘echoed’ to screen – Some editing is allowed (e.g., backspace) – The keyboard-input is internally buffered – The <ENTER>-key signals an ‘end-of-line’ – Programs receive input one-line-at-a-time ‘tty’ customization • Sometimes canonical mode isn’t suitable (an example: animated computer games) • The terminal’s behavior can be modified! • UNIX provides a convenient interface: – #include <termios.h> – struct termios tty; – int tcgetattr( int fd, struct termios *tty ); – int tcsetattr( int fd, int flag, struct termios *tty ); How does the ‘tty’ work? SOFTWARE application User space tty_driver c_lflag Kernel space input handling c_iflag c_cc output handling c_oflag terminal_driver c_cflag HARDWARE TeleTYpe display device struct tty { c_iflag; c_oflag; c_cflag; c_lflag; c_line; c_cc[ ]; }; The ‘c_lflag’ field • • • • • • This field is just an array of flag bits Individual bits have symbolic names Names conform to a POSIX standard Linux names match other UNIX’s names Though actual symbol values may differ Your C/C++ program should use: #include <termios.h> for portability to other UNIX environments ICANON and ECHO • Normally the ‘c_lflag’ field has these set • They can be cleared using bitwise logic: tty.c_lflag &= ~ECHO; // inhibit echo tty.c_lflag &= ~ICANON; // no buffering The ‘c_cc[ ]’ array • • • • • ‘struct termios’ objects include an array The array-indices have symbolic names Symbol-names are standardized in UNIX Array entries are ‘tty’ operating parameters Two useful ones for our purposes are: tty.c_cc[ VMIN ] and tty.c_cc[ VTIME ] How to setup ‘raw’ terminal-mode • Step 1: Use ‘tcgetattr()’ to get a copy of the current tty’s ‘struct termios’ settings • Step 2: Make a working copy of that object • Step 3: Modify its flags and control-codes • Step 4: Use ‘tcsetattr()’ to install changes • Step 5: Perform desired ‘raw’ mode input • Step 6: Use ‘tcsetattr()’ to restore the terminal to its original default settings ‘raw’ mode needs four changes • tty.c_cc[ VMIN ] = 1; – so the ‘read()’ function will return as soon as at least one new input-character is available • tty.c_cc[ VTIME ] = 0; – so there will be no time-delay after each new key pressed until the ‘read()’ function returns • tty.c_lflag &= ~ECHO; • tty.c_lflag &= ~ICANON; // no echoing // no buffering Demo program: ‘rawtty.cpp’ • • • • • This program may soon prove useful It shows the keyboard scancode values It demonstrates ‘noncanonical’ tty mode It clears the ISIG bit (in ‘c_lflags’ field) This prevents <CONTROL>-C from being used to abort the program: the user must ‘quit’ by hitting the <ESCAPE>-key; so default terminal-settings will get reinstalled In-class Exercise • Use the Linux ‘tty’ interface-functions to reprogram your terminal for ‘raw’ input • Draw ramdom color-filled circles -- until your user hits the <ESCAPE> key • Algorithm: int done = 0; do { draw_next_circle( x, y, r, color ); int inch = 0; read( 0, &inch, 4 ); if ( inch == 0x1B ) done = 1; } while ( !done );