/** * ansi_graphics.h * A couple of function to display graphics in the terminal, * using ansi sequences. * Bruno Levy, Jan 2024 */ #include #include #include #include #ifndef GL_FPS #define GL_FPS 30 #endif #if defined(__linux__) || defined(_WIN32) || defined(__APPLE__) #define BIGCPU // we are compiling for a real machine #else #define TINYCPU // we are compiling for a softwore #endif #ifdef __linux__ #include // for usleep() #endif // You can define GL_width and GL_height before // #including ansi_graphics.h in case the plain // old 80x25 pixels does not suffice. #ifndef GL_width #define GL_width 80 #endif #ifndef GL_height #define GL_height 25 #endif /** * \brief Sets the current graphics position * \param[in] x typically in 0,79 * \param[in] y typically in 0,24 */ static inline void GL_gotoxy(int x, int y) { printf("\033[%d;%dH",y,x); } /** * \brief Sets the current graphics position * \param[in] R , G , B the RGB color of the pixel, in [0..255] * \details Typically used by programs that draw all pixels sequentially, * like a raytracer. After each line, one can either printf("\n") or * call GL_gotoxy(). If you want to draw individual pixels in an * arbitrary order, use GL_setpixelRGB(x,y,R,G,B) */ static inline void GL_setpixelRGBhere(uint8_t R, uint8_t G, uint8_t B) { // set background color, print space printf("\033[48;2;%d;%d;%dm ",(int)R,(int)G,(int)B); } /** * \brief Draws two "pixels" at the current * cursor position and advances the current cursor * position. * \details Characters are roughly twice as high as wide. * To generate square pixels, this function draws two pixels in * the same character, using the special lower-half white / upper-half * black character, and setting the background and foreground colors. */ static inline void GL_set2pixelsRGBhere( uint8_t r1, uint8_t g1, uint8_t b1, uint8_t r2, uint8_t g2, uint8_t b2 ) { if((r2 == r1) && (g2 == g1) && (b2 == b1)) { GL_setpixelRGBhere(r1,g1,b1); } else { printf("\033[48;2;%d;%d;%dm",(int)r1,(int)g1,(int)b1); printf("\033[38;2;%d;%d;%dm",(int)r2,(int)g2,(int)b2); // https://www.w3.org/TR/xml-entity-names/025.html // https://onlineunicodetools.com/convert-unicode-to-utf8 // https://copypastecharacter.com/ printf("\xE2\x96\x83"); } } #define GL_RGB(R,G,B) #R ";" #G ";" #B static inline void GL_setpixelIhere( const char** cmap, int c ) { // set background color, print space printf("\033[48;2;%sm ",cmap[c]); } static inline void GL_set2pixelsIhere( const char** cmap, int c1, int c2 ) { if(c1 == c2) { GL_setpixelIhere(cmap, c1); } else { printf("\033[48;2;%sm",cmap[c1]); printf("\033[38;2;%sm",cmap[c2]); // https://www.w3.org/TR/xml-entity-names/025.html // https://onlineunicodetools.com/convert-unicode-to-utf8 // https://copypastecharacter.com/ printf("\xE2\x96\x83"); } } /** * \brief Moves the cursor position to the next line. * \details Background and foreground colors are set to black. */ static inline void GL_newline() { printf("\033[38;2;0;0;0m"); printf("\033[48;2;0;0;0m\n"); } /** * \brief Sets the color of a pixel * \param[in] x typically in 0,79 * \param[in] y typically in 0,24 * \param[in] R , G , B the RGB color of the pixel, in [0..255] */ static inline void GL_setpixelRGB( int x, int y, uint8_t R, uint8_t G, uint8_t B ) { GL_gotoxy(x,y); GL_setpixelRGBhere(R,G,B); } /** * \brief restore default foreground and background colors */ static inline void GL_restore_default_colors() { printf( "\033[48;5;16m" // set background color black "\033[38;5;15m" // set foreground color white ); } /** * \brief Call this function each time graphics should be cleared */ static inline void GL_clear() { GL_restore_default_colors(); printf("\033[2J"); // clear screen } /** * \brief Moves current drawing position to top-left corner * \see GL_setpixelRGBhere() and GL_set2pixelsRGBhere() */ static inline void GL_home() { printf("\033[H"); } /** * \brief Call this function before starting drawing graphics * or each time graphics should be cleared */ static inline void GL_init() { printf("\033[?25l"); // hide cursor GL_home(); GL_clear(); } /** * \brief Call this function at the end of the program */ static inline void GL_terminate() { GL_restore_default_colors(); GL_gotoxy(0,GL_height); printf("\033[?25h"); // show cursor } /** * \brief Flushes pending graphic operations and waits a bit */ static inline void GL_swapbuffers() { // only flush if we are on a big machine, with true stdio support // otherwise does nothing (because our small MCU io lib is not buffered) #ifdef BIGCPU fflush(stdout); #endif #ifdef __linux__ usleep(1000000/GL_FPS); #endif } typedef void (*GL_pixelfunc_RGB)(int x, int y, uint8_t* r, uint8_t* g, uint8_t* b); typedef void (*GL_pixelfunc_RGBf)(int x, int y, float* r, float* g, float* b); /** * \brief Draws an image by calling a user-specified function for each pixel. * \param[in] width , height dimension of the image in square pixels * \param[in] do_pixel the user function to be called for each pixel * (a "shader"), that determines the (integer) components r,g,b of * the pixel's color. * \details Uses half-charater pixels. */ static inline void GL_scan_RGB( int width, int height, GL_pixelfunc_RGB do_pixel ) { uint8_t r1, g1, b1; uint8_t r2, g2, b2; GL_home(); for (int j = 0; j 1.0f) ? 1.0f : f; return (uint8_t)(255.0f * f); } /** * \brief Draws an image by calling a user-specified function for each pixel. * \param[in] width , height dimension of the image in square pixels * \param[in] do_pixel the user function to be called for each pixel * (a "shader"), that determines the (floating-point) components * fr,fg,fb of the pixel's color. * \details Uses half-charater pixels. */ static inline void GL_scan_RGBf( int width, int height, GL_pixelfunc_RGBf do_pixel ) { float fr1, fg1, fb1; float fr2, fg2, fb2; uint8_t r1, g1, b1; uint8_t r2, g2, b2; GL_home(); for (int j = 0; j XMAX)<<1) | (((y) < YMIN)<<2) | (((y) > YMAX)<<3) /***************************************************************/ static inline void GL_line( int x1, int y1, int x2, int y2, int R, int G, int B ) { int x,y,dx,dy,sx,sy,tmp; /* Cohen-Sutherland line clipping. */ int code1 = code(x1,y1); int code2 = code(x2,y2); int codeout; for(;;) { /* Both points inside. */ if(code1 == 0 && code2 == 0) { break; } /* No point inside. */ if(code1 & code2) { return; } /* One of the points is outside. */ codeout = code1 ? code1 : code2; /* Compute intersection. */ if (codeout & TOP) { x = x1 + (x2 - x1) * (YMAX - y1) / (y2 - y1); y = YMAX; } else if (codeout & BOTTOM) { x = x1 + (x2 - x1) * (YMIN - y1) / (y2 - y1); y = YMIN; } else if (codeout & RIGHT) { y = y1 + (y2 - y1) * (XMAX - x1) / (x2 - x1); x = XMAX; } else if (codeout & LEFT) { y = y1 + (y2 - y1) * (XMIN - x1) / (x2 - x1); x = XMIN; } /* Replace outside point with intersection. */ if (codeout == code1) { x1 = x; y1 = y; code1 = code(x1,y1); } else { x2 = x; y2 = y; code2 = code(x2,y2); } } // Swap both extremities to ensure x increases if(x2 < x1) { tmp = x2; x2 = x1; x1 = tmp; tmp = y2; y2 = y1; y1 = tmp; } // Bresenham line drawing. dy = y2 - y1; sy = 1; if(dy < 0) { sy = -1; dy = -dy; } dx = x2 - x1; x = x1; y = y1; if(dy > dx) { int ex = (dx << 1) - dy; for(int u=0; u= 0) { x++; ex -= dy << 1; GL_setpixelRGB(x,y,R,G,B); } while(ex >= 0) { x++; ex -= dy << 1; putchar(' '); } ex += dx << 1; } } else { int ey = (dy << 1) - dx; for(int u=0; u= 0) { y += sy; ey -= dx << 1; GL_setpixelRGB(x,y,R,G,B); } ey += dy << 1; } } } /***************************************************************/ #ifdef GL_USE_TURTLE #include "sintab.h" // Ugly !!! typedef struct { int x; // in [0..79] int y; // in [0..24] int angle; // in degrees int R,G,B; // pen color int pendown; // draw if non-zero } Turtle; static inline void Turtle_init(Turtle* T) { T->x = GL_width/2; T->y = GL_height/2; T->angle = -90; T->pendown = 1; T->R = 255; T->G = 255; T->B = 255; } static inline void Turtle_pen_up(Turtle* T) { T->pendown = 0; } static inline void Turtle_pen_down(Turtle* T) { T->pendown = 1; } static inline void Turtle_pen_color(Turtle* T, int R, int G, int B) { T->R = R; T->G = G; T->B = B; } static inline void Turtle_forward(Turtle* T, int distance) { int last_x = T->x; int last_y = T->y; int a = T->angle; while(a < 0) { a += 360; } while(a > 360) { a -= 360; } T->x += (costab[a] * distance) / 256; T->y += (sintab[a] * distance) / 256; if(T->pendown) { GL_line(last_x, last_y, T->x, T->y, T->R, T->G, T->B); } } static inline void Turtle_backward(Turtle* T, int distance) { Turtle_forward(T,-distance); } static inline void Turtle_turn_right(Turtle* T, int delta_angle) { T->angle += delta_angle; } static inline void Turtle_turn_left(Turtle* T, int delta_angle) { Turtle_turn_right(T, -delta_angle); } #endif