#define _GNU_SOURCE #include #include #include #include #include #include #include /** * Allocate a new line. * * @return the new allocated line object. */ line_t * line_new( void ) { line_t *line = calloc( 1, sizeof(*line) ); OBJECT_TYPE( line ) = OBJECT_TYPE_LINE; return line; } /** * Free memory allocated to line. * * @param an allocated line object. */ void line_free( line_t *line ) { assert( line != NULL ); free( line ); } /** * Return the string representation of this line object. * * @note string is malloc()ated, please free() it later. * * @param line the object to act on. * * @return string representation. */ char * line_str( line_t *line ) { char *s = NULL; int r; if ( line == NULL ) r = asprintf( &s, "Line is NULL!" ); else r = asprintf( &s, "Line( depth=%d, color=0x%08x, transp=%0.3f, " "p0=( %d, %d ), p1=( %d, %d ) )", OBJECT_DEPTH( line ), COLOR_TO_ARGB( OBJECT_COLOR( line ) ), OBJECT_TRANSP( line ), line->r.p0.x, line->r.p0.y, line->r.p1.x, line->r.p1.y ); if ( r < 0 ) s = NULL; return s; } void line_draw_vertical( const uint16_t x, const uint16_t y_min, uint16_t y_max, const surface_t *surface, const color_t color, const float transp ) { assert( surface != NULL ); assert( surface->buffer != NULL ); assert( y_max >= y_min ); uint16_t w = surface->size.x; uint16_t h = surface->size.y; /* doesn't draw outside buffer */ if ( x >= w ) return; if ( y_max >= h ) y_max = h; if ( y_min > y_max ) return; /* move buffer pointer to (x, y_min ) */ color_t *buf = &( PIXEL_AT( surface, x, y_min ) ); uint16_t y; for ( y=y_min; y < y_max; y++ ) { pixel_paint( buf, color, transp ); buf += w; /* go to the next line, same x */ } } void line_draw_horizontal( const uint16_t x_min, uint16_t x_max, const uint16_t y, const surface_t *surface, const color_t color, const float transp ) { assert( surface != NULL ); assert( surface->buffer != NULL ); assert( x_max >= x_min ); uint16_t w = surface->size.x; uint16_t h = surface->size.y; /* doesn't draw outside buffer */ if ( y >= h ) return; if ( x_max >= w ) x_max = w - 1; if ( x_min > x_max ) return; color_t *buf = &( PIXEL_AT( surface, x_min, y ) ); color_t *end = buf + ( x_max - x_min ); for (; buf != end; buf ++ ) pixel_paint( buf, color, transp ); } /** * Internal function to draw lines using the trivial algorithm (floating point) * * @note It doesn't handle special cases: vertical and horizontal lines * @note It assumes x1 > x0 */ void line_draw_trivial( uint16_t x0, uint16_t x1, uint16_t y0, uint16_t y1, surface_t *surface, color_t color, float transp ) { assert( surface != NULL ); assert( surface->buffer != NULL ); assert( x1 > x0 ); assert( y1 != y0 ); int dx = x1 - x0; int dy = y1 - y0; if ( dx > abs( dy ) ) { float m = (float)dy / (float)dx; float y = y0; uint_fast16_t x; for ( x=x0; x <= x1; x++, y += m ) surface_draw_pixel( surface, x, y, color, transp ); } else { /* we have more points on y than on x, invert axis */ float m = (float)dx / (float)dy; if ( dy < 0 ) { dy = -dy; /* useless */ swap( x0, x1 ); swap( y0, y1 ); } float x = x0; uint_fast16_t y; for ( y=y0; y <= y1; y++, x += m ) surface_draw_pixel( surface, x, y, color, transp ); } } /** * Internal function to draw lines using the trivial algorithm (floating point) * * @note It doesn't handle special cases: vertical and horizontal lines * @note It assumes x1 > x0 */ void line_draw_trivial_antialias( uint16_t x0, uint16_t x1, uint16_t y0, uint16_t y1, surface_t *surface, color_t color, float transp ) { assert( surface != NULL ); assert( surface->buffer != NULL ); assert( x1 > x0 ); assert( y1 != y0 ); int dx = x1 - x0; int dy = y1 - y0; if ( dx > abs( dy ) ) { float m = (float)dy / (float)dx; float y = y0; uint_fast16_t x; for ( x=x0; x <= x1; x++, y += m ) { float a = y - floor( y ); surface_draw_pixel( surface, x, y, color, a + transp ); surface_draw_pixel( surface, x, y + 1, color, 1 - a + transp ); } } else { /* we have more points on y than on x, invert axis */ float m = (float)dx / (float)dy; if ( dy < 0 ) { dy = -dy; /* useless */ swap( x0, x1 ); swap( y0, y1 ); } float x = x0; uint_fast16_t y; for ( y=y0; y <= y1; y++, x += m ) { float a = x - floor( x ); surface_draw_pixel( surface, x, y, color, a + transp ); surface_draw_pixel( surface, x + 1, y, color, 1 - a + transp ); } } } /** * Internal function to draw lines using the Bresenham (integer only) * algorithm. * * @note It doesn't handle special case: vertical line * @note It assumes x1 > x0 */ void line_draw_bresenham( uint16_t x0, uint16_t x1, uint16_t y0, uint16_t y1, surface_t *surface, color_t color, float transp ) { assert( surface != NULL ); assert( surface->buffer != NULL ); assert( x1 > x0 ); int dx = x1 - x0; int dy = y1 - y0; int_fast8_t step_y; if ( dy >= 0 ) step_y = 1; else { dy = -dy; step_y = -1; } if ( dx > dy ) { int e = - ( dx / 2 ); uint_fast16_t x; uint_fast16_t y = y0; for ( x=x0; x <= x1; x++ ) { surface_draw_pixel( surface, x, y, color, transp ); e += dy; if ( e >= 0 ) { y += step_y; e -= dx; } } } else { int e = - ( dy / 2 ); uint_fast16_t y; uint_fast16_t x = x0; for ( y=y0; y != y1; y += step_y ) { surface_draw_pixel( surface, x, y, color, transp ); e += dx; if ( e >= 0 ) { x ++; e -= dy; } } } } void (*line_draw_aliased)( uint16_t, uint16_t, uint16_t, uint16_t, surface_t *, color_t, float ) = line_draw_bresenham; void (*line_draw_antialiased)( uint16_t, uint16_t, uint16_t, uint16_t, surface_t *, color_t, float ) = line_draw_trivial_antialias; /** * Generic line drawing algoritm. * * It handle special cases, like vertical and horizontal, does swap * to ensure x1 > x0 and chooses the algorithm to do anti-alias or aliased * drawings. * * You may toggle drawing using line_draw_aliased and line_draw_antialiased * variables. * * @param line the line object to draw. * @param surface where to draw the object. */ void __line_draw( line_t *line, surface_t *surface ) { assert( surface != NULL ); assert( surface->buffer != NULL ); uint16_t x0 = line->r.p0.x; uint16_t x1 = line->r.p1.x; uint16_t y0 = line->r.p0.y; uint16_t y1 = line->r.p1.y; color_t color = OBJECT_COLOR( line ); float transp = OBJECT_TRANSP( line ); int dx = x1 - x0; int dy = y1 - y0; if ( dx == 0 ) { /* Special case: vertical line. Line inclination is infinite dy/0 */ if ( dy >= 0 ) line_draw_vertical( x0, y0, y1, surface, color, transp ); else line_draw_vertical( x0, y1, y0, surface, color, transp ); } else if ( dy == 0 ) { /* Special case: speed up horizontal line drawing */ if ( dx >= 0 ) line_draw_horizontal( x0, x1, y0, surface, color, transp ); else line_draw_horizontal( x1, x0, y0, surface, color, transp ); } else { if ( OBJECT_DRAW_OPTS( line ) & DRAW_ANTIALIAS ) { if ( dx >= 0 ) line_draw_antialiased( x0, x1, y0, y1, surface, color, transp ); else line_draw_antialiased( x1, x0, y1, y0, surface, color, transp ); } else { if ( dx >= 0 ) line_draw_aliased( x0, x1, y0, y1, surface, color, transp ); else line_draw_aliased( x1, x0, y1, y0, surface, color, transp ); } } } void line_draw( line_t *line, surface_t *surface ) { surface_reset_visits( surface ); __line_draw( line, surface ); }