How to draw circles, arcs and vector graphics in SDL?
Asked Answered
C

5

25

I'm using SDL2.

The only way I can find to draw a shape is with the line, rect and pixel functions, as explained here.

Apart from using trig or the "equation of a circle", how could I draw a curve? How about general vector graphics?

Is SDL an appropriate starting point or should I look elsewhere?

Cosmonaut answered 12/7, 2016 at 16:11 Comment(0)
M
24

If you want to write your own circle drawing function, then I'd suggest adapting the midpoint algorithm to SDL2 by drawing pixels.

Curves would be done similarly, but would use more of an ellipses drawing algorithm.

Actual vector graphics start to get much more complicated, and you'd probably have to find something that renders SVG files, which I'm not sure there are many options for SDL2.

However, if you would rather simply have functions that you can work with I'd suggest going straight to SDL2_gfx instead. It has many more functions already implemented for you to work with.

Mervinmerwin answered 12/7, 2016 at 17:51 Comment(0)
C
33

This is an example of the Midpoint Circle Algorithm as referenced above. It doesn't require a math library and is very fast. (Renders in about 500 microseconds) This is what Windows uses/used to rasterize circles.

void DrawCircle(SDL_Renderer * renderer, int32_t centreX, int32_t centreY, int32_t radius)
{
   const int32_t diameter = (radius * 2);

   int32_t x = (radius - 1);
   int32_t y = 0;
   int32_t tx = 1;
   int32_t ty = 1;
   int32_t error = (tx - diameter);

   while (x >= y)
   {
      //  Each of the following renders an octant of the circle
      SDL_RenderDrawPoint(renderer, centreX + x, centreY - y);
      SDL_RenderDrawPoint(renderer, centreX + x, centreY + y);
      SDL_RenderDrawPoint(renderer, centreX - x, centreY - y);
      SDL_RenderDrawPoint(renderer, centreX - x, centreY + y);
      SDL_RenderDrawPoint(renderer, centreX + y, centreY - x);
      SDL_RenderDrawPoint(renderer, centreX + y, centreY + x);
      SDL_RenderDrawPoint(renderer, centreX - y, centreY - x);
      SDL_RenderDrawPoint(renderer, centreX - y, centreY + x);

      if (error <= 0)
      {
         ++y;
         error += ty;
         ty += 2;
      }

      if (error > 0)
      {
         --x;
         tx += 2;
         error += (tx - diameter);
      }
   }
}
Critique answered 16/1, 2018 at 23:47 Comment(0)
M
24

If you want to write your own circle drawing function, then I'd suggest adapting the midpoint algorithm to SDL2 by drawing pixels.

Curves would be done similarly, but would use more of an ellipses drawing algorithm.

Actual vector graphics start to get much more complicated, and you'd probably have to find something that renders SVG files, which I'm not sure there are many options for SDL2.

However, if you would rather simply have functions that you can work with I'd suggest going straight to SDL2_gfx instead. It has many more functions already implemented for you to work with.

Mervinmerwin answered 12/7, 2016 at 17:51 Comment(0)
C
6

SDL allows for third party libs to draw on a texture. If cairo was desirable, it could be used in a function like this:

cairo_t*cb(cairo_t*cr)
{cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
 cairo_rectangle(cr, 10, 20, 128, 128);
 cairo_stroke(cr);
 return cr;
}

then cb can be passed to this function:

cairo_t*cai(SDL_Window*w,SDL_Renderer*r,cairo_t*(*f)(cairo_t*))
{int width, height, pitch;void *pixels;
 SDL_GetWindowSize(w, &width, &height);
 SDL_Texture*t=SDL_CreateTexture(r,SDL_PIXELFORMAT_ARGB8888,SDL_TEXTUREACCESS_STREAMING,width,height);
 SDL_LockTexture(t, NULL, &pixels, &pitch);
 cairo_surface_t *cs=cairo_image_surface_create_for_data(pixels,CAIRO_FORMAT_ARGB32,width,height,pitch);
 cairo_t*s=cairo_create(cs);
 cairo_t*fr=f(s);SDL_UnlockTexture(t);SDL_RenderCopy(r,t,NULL,NULL);SDL_RenderPresent(r);
 return fr;
}
Cosmonaut answered 24/7, 2016 at 8:27 Comment(0)
L
4

If you want to do a circle or ellipse without 3rd party libraries, include math.h and use the function below I wrote. It will draw aliased ellipse or circles very well. Tested on SDL 2.0.2 and works. It draws one quadrant arc, and mirrors the other arcs, reducing calls to cosf and sinf.

//draw one quadrant arc, and mirror the other 4 quadrants
void sdl_ellipse(SDL_Renderer* r, int x0, int y0, int radiusX, int radiusY)
{
    float pi  = 3.14159265358979323846264338327950288419716939937510;
    float pih = pi / 2.0; //half of pi

    //drew  28 lines with   4x4  circle with precision of 150 0ms
    //drew 132 lines with  25x14 circle with precision of 150 0ms
    //drew 152 lines with 100x50 circle with precision of 150 3ms
    const int prec = 27; // precision value; value of 1 will draw a diamond, 27 makes pretty smooth circles.
    float theta = 0;     // angle that will be increased each loop

    //starting point
    int x  = (float)radiusX * cos(theta);//start point
    int y  = (float)radiusY * sin(theta);//start point
    int x1 = x;
    int y1 = y;

    //repeat until theta >= 90;
    float step = pih/(float)prec; // amount to add to theta each time (degrees)
    for(theta=step;  theta <= pih;  theta+=step)//step through only a 90 arc (1 quadrant)
    {
        //get new point location
        x1 = (float)radiusX * cosf(theta) + 0.5; //new point (+.5 is a quick rounding method)
        y1 = (float)radiusY * sinf(theta) + 0.5; //new point (+.5 is a quick rounding method)

        //draw line from previous point to new point, ONLY if point incremented
        if( (x != x1) || (y != y1) )//only draw if coordinate changed
        {
            SDL_RenderDrawLine(r, x0 + x, y0 - y,    x0 + x1, y0 - y1 );//quadrant TR
            SDL_RenderDrawLine(r, x0 - x, y0 - y,    x0 - x1, y0 - y1 );//quadrant TL
            SDL_RenderDrawLine(r, x0 - x, y0 + y,    x0 - x1, y0 + y1 );//quadrant BL
            SDL_RenderDrawLine(r, x0 + x, y0 + y,    x0 + x1, y0 + y1 );//quadrant BR
        }
        //save previous points
        x = x1;//save new previous point
        y = y1;//save new previous point
    }
    //arc did not finish because of rounding, so finish the arc
    if(x!=0)
    {
        x=0;
        SDL_RenderDrawLine(r, x0 + x, y0 - y,    x0 + x1, y0 - y1 );//quadrant TR
        SDL_RenderDrawLine(r, x0 - x, y0 - y,    x0 - x1, y0 - y1 );//quadrant TL
        SDL_RenderDrawLine(r, x0 - x, y0 + y,    x0 - x1, y0 + y1 );//quadrant BL
        SDL_RenderDrawLine(r, x0 + x, y0 + y,    x0 + x1, y0 + y1 );//quadrant BR
    }
}
Lilian answered 12/12, 2017 at 5:56 Comment(1)
Would it be good if prec was based on radius values instead of always the same? This would increase quality for big ellipses but maintain speed for smaller ones.Pseudohermaphrodite
B
4

My answer extends Scotty Stephens answer by making it a bunch more performant by reducing the API calls to a single one.

// rounding helper, simplified version of the function I use
int roundUpToMultipleOfEight( int v )
{
    return (v + (8 - 1)) & -8;
}

void DrawCircle( SDL_Renderer * renderer, SDL_Point center, int radius )
{
    // 35 / 49 is a slightly biased approximation of 1/sqrt(2)
    const int arrSize = roundUpToMultipleOfEight( radius * 8 * 35 / 49 );
    SDL_Point points[arrSize];
    int       drawCount = 0;

    const int32_t diameter = (radius * 2);

    int32_t x = (radius - 1);
    int32_t y = 0;
    int32_t tx = 1;
    int32_t ty = 1;
    int32_t error = (tx - diameter);

    while( x >= y )
    {
        // Each of the following renders an octant of the circle
        points[drawCount+0] = { center.x + x, center.y - y };
        points[drawCount+1] = { center.x + x, center.y + y };
        points[drawCount+2] = { center.x - x, center.y - y };
        points[drawCount+3] = { center.x - x, center.y + y };
        points[drawCount+4] = { center.x + y, center.y - x };
        points[drawCount+5] = { center.x + y, center.y + x };
        points[drawCount+6] = { center.x - y, center.y - x };
        points[drawCount+7] = { center.x - y, center.y + x };

        drawCount += 8;

        if( error <= 0 )
        {
            ++y;
            error += ty;
            ty += 2;
        }

        if( error > 0 )
        {
            --x;
            tx += 2;
            error += (tx - diameter);
        }
    }

    SDL_RenderDrawPoints( renderer, points, drawCount );
}

A circle of radius 141 would have had 800 SDL_RenderDrawPoint calls in Scottys version, this new version does only execute one single SDL_RenderDrawPoints call, making it much more performant.

One could also strip the rendering portion out of this function, to allow the result to be cached and reused like shown below.

std::vector<SDL_Point> PixelizeCircle( SDL_Point center, int radius )
{
    std::vector<SDL_Point> points;

    // 35 / 49 is a slightly biased approximation of 1/sqrt(2)
    const int arrSize   = roundUpToMultipleOfEight( radius * 8 * 35 / 49 );
    points.reserve( arrSize );

    const int32_t diameter = (radius * 2);

    int32_t x = (radius - 1);
    int32_t y = 0;
    int32_t tx = 1;
    int32_t ty = 1;
    int32_t error = (tx - diameter);

    while( x >= y )
    {
        // Each of the following renders an octant of the circle
        points.push_back( { center.x + x, center.y - y } );
        points.push_back( { center.x + x, center.y + y } );
        points.push_back( { center.x - x, center.y - y } );
        points.push_back( { center.x - x, center.y + y } );
        points.push_back( { center.x + y, center.y - x } );
        points.push_back( { center.x + y, center.y + x } );
        points.push_back( { center.x - y, center.y - x } );
        points.push_back( { center.x - y, center.y + x } );

        if( error <= 0 )
        {
            ++y;
            error += ty;
            ty += 2;
        }

        if( error > 0 )
        {
            --x;
            tx += 2;
            error += (tx - diameter);
        }
    }

    return points; // RVO FTW
}

int main()
{
    std::vector<SDL_Point> circle = PixelizeCircle( SDL_Point{ 84, 72 }, 79 );
    //...
    while( true )
    {
        //...
        SDL_RenderDrawPoints( renderer, circle.data(), circle.size() );
        //...
    }
}
Bennir answered 9/12, 2022 at 15:28 Comment(2)
how were you able to allocate the array SDL_Point points[arrSize]; when arrSize is not a constexpr? my compiler says, expression must have a constant value\nthe value of "arrSize" (declared at line n) cannot be used as a constant.Intoxicative
Hey @CeeMcSharpface, sorry for the late answer, I encountered the same problem later on as well. It turned out that the first code example works fine with a C compiler and permissive C++ compilers, although VLA (en.wikipedia.org/wiki/Variable-length_array) is not supported in standard C++. For C++ you can substitute the usage of std::vector of the second example in the first.Bennir

© 2022 - 2025 — McMap. All rights reserved.