Draw shapes
This program draws lines and points, and it shows three shapes in outline and filled. The first row is outlines, and the second row is filled. You learn that SDL draws only a few simple things, and that you build the rest from them.
The full program
/* Draw lines, points, and outlined and filled shapes. */
#define SDL_MAIN_USE_CALLBACKS
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#define WINDOW_WIDTH 640
#define WINDOW_HEIGHT 480
#define TOP_ROW 130.0f
#define BOTTOM_ROW 350.0f
#define RADIUS 60.0f
#define CIRCLE_SEGMENTS 48
static SDL_Window *window = NULL;
static SDL_Renderer *renderer = NULL;
/* Join six points with five lines. */
static void draw_zigzag(float x, float y)
{
SDL_FPoint points[6];
for (int i = 0; i < 6; i++) {
points[i].x = x - RADIUS + (float)i * 24.0f;
points[i].y = y + ((i % 2 == 0) ? -RADIUS : RADIUS);
}
SDL_RenderLines(renderer, points, 6);
}
/* A grid of single pixels, one every 12 pixels. */
static void draw_dots(float x, float y)
{
for (float dy = -RADIUS; dy <= RADIUS; dy += 12.0f) {
for (float dx = -RADIUS; dx <= RADIUS; dx += 12.0f) {
SDL_RenderPoint(renderer, x + dx, y + dy);
}
}
}
/* Short straight lines look like a curve. The last point closes the circle. */
static void draw_circle(float x, float y, float radius)
{
SDL_FPoint points[CIRCLE_SEGMENTS + 1];
for (int i = 0; i <= CIRCLE_SEGMENTS; i++) {
float angle = (float)i / CIRCLE_SEGMENTS * 2.0f * SDL_PI_F;
points[i].x = x + radius * SDL_cosf(angle);
points[i].y = y + radius * SDL_sinf(angle);
}
SDL_RenderLines(renderer, points, CIRCLE_SEGMENTS + 1);
}
/* Draw one line across the circle for each row of pixels. */
static void fill_circle(float x, float y, float radius)
{
for (float dy = -radius; dy <= radius; dy += 1.0f) {
float half_width = SDL_sqrtf(radius * radius - dy * dy);
SDL_RenderLine(renderer, x - half_width, y + dy, x + half_width, y + dy);
}
}
/* Three corners, then the first corner again to close the shape. */
static void draw_triangle(float x, float y)
{
SDL_FPoint points[4] = {
{ x, y - RADIUS },
{ x + RADIUS, y + RADIUS },
{ x - RADIUS, y + RADIUS },
{ x, y - RADIUS }
};
SDL_RenderLines(renderer, points, 4);
}
/* The graphics card fills a triangle from three corners, called vertices.
A color on each vertex blends across the shape. */
static void fill_triangle(float x, float y)
{
SDL_Vertex vertices[3] = {
{ { x, y - RADIUS }, { 1.0f, 0.6f, 0.35f, 1.0f }, { 0.0f, 0.0f } },
{ { x + RADIUS, y + RADIUS }, { 0.15f, 0.45f, 0.85f, 1.0f }, { 0.0f, 0.0f } },
{ { x - RADIUS, y + RADIUS }, { 0.3f, 0.8f, 0.55f, 1.0f }, { 0.0f, 0.0f } }
};
SDL_RenderGeometry(renderer, NULL, vertices, 3, NULL, 0);
}
SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
{
if (!SDL_Init(SDL_INIT_VIDEO)) {
SDL_Log("SDL_Init failed: %s", SDL_GetError());
return SDL_APP_FAILURE;
}
if (!SDL_CreateWindowAndRenderer("Draw shapes", WINDOW_WIDTH, WINDOW_HEIGHT, 0,
&window, &renderer)) {
SDL_Log("SDL_CreateWindowAndRenderer failed: %s", SDL_GetError());
return SDL_APP_FAILURE;
}
SDL_SetRenderVSync(renderer, 1);
return SDL_APP_CONTINUE;
}
SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event)
{
if (event->type == SDL_EVENT_QUIT) {
return SDL_APP_SUCCESS;
}
return SDL_APP_CONTINUE;
}
SDL_AppResult SDL_AppIterate(void *appstate)
{
SDL_FRect outline = { 180.0f, TOP_ROW - RADIUS, RADIUS * 2.0f, RADIUS * 2.0f };
SDL_FRect filled = { 180.0f, BOTTOM_ROW - RADIUS, RADIUS * 2.0f, RADIUS * 2.0f };
SDL_SetRenderDrawColor(renderer, 24, 26, 32, SDL_ALPHA_OPAQUE);
SDL_RenderClear(renderer);
SDL_SetRenderDrawColor(renderer, 235, 236, 240, SDL_ALPHA_OPAQUE);
draw_zigzag(80.0f, TOP_ROW);
draw_dots(80.0f, BOTTOM_ROW);
SDL_SetRenderDrawColor(renderer, 255, 157, 92, SDL_ALPHA_OPAQUE);
SDL_RenderRect(renderer, &outline);
SDL_RenderFillRect(renderer, &filled);
SDL_SetRenderDrawColor(renderer, 80, 200, 160, SDL_ALPHA_OPAQUE);
draw_circle(400.0f, TOP_ROW, RADIUS);
fill_circle(400.0f, BOTTOM_ROW, RADIUS);
SDL_SetRenderDrawColor(renderer, 120, 170, 240, SDL_ALPHA_OPAQUE);
draw_triangle(550.0f, TOP_ROW);
fill_triangle(550.0f, BOTTOM_ROW);
SDL_RenderPresent(renderer);
return SDL_APP_CONTINUE;
}
void SDL_AppQuit(void *appstate, SDL_AppResult result)
{
}/* Draw lines, points, and outlined and filled shapes. */
#include <SDL.h>
#include <stdbool.h>
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif
#define WINDOW_WIDTH 640
#define WINDOW_HEIGHT 480
#define TOP_ROW 130.0f
#define BOTTOM_ROW 350.0f
#define RADIUS 60.0f
#define CIRCLE_SEGMENTS 48
static SDL_Window *window = NULL;
static SDL_Renderer *renderer = NULL;
static bool running = true;
/* Join six points with five lines. */
static void draw_zigzag(float x, float y)
{
SDL_FPoint points[6];
for (int i = 0; i < 6; i++) {
points[i].x = x - RADIUS + (float)i * 24.0f;
points[i].y = y + ((i % 2 == 0) ? -RADIUS : RADIUS);
}
SDL_RenderDrawLinesF(renderer, points, 6);
}
/* A grid of single pixels, one every 12 pixels. */
static void draw_dots(float x, float y)
{
for (float dy = -RADIUS; dy <= RADIUS; dy += 12.0f) {
for (float dx = -RADIUS; dx <= RADIUS; dx += 12.0f) {
SDL_RenderDrawPointF(renderer, x + dx, y + dy);
}
}
}
/* Short straight lines look like a curve. The last point closes the circle. */
static void draw_circle(float x, float y, float radius)
{
SDL_FPoint points[CIRCLE_SEGMENTS + 1];
for (int i = 0; i <= CIRCLE_SEGMENTS; i++) {
float angle = (float)i / CIRCLE_SEGMENTS * 2.0f * (float)M_PI;
points[i].x = x + radius * SDL_cosf(angle);
points[i].y = y + radius * SDL_sinf(angle);
}
SDL_RenderDrawLinesF(renderer, points, CIRCLE_SEGMENTS + 1);
}
/* Draw one line across the circle for each row of pixels. */
static void fill_circle(float x, float y, float radius)
{
for (float dy = -radius; dy <= radius; dy += 1.0f) {
float half_width = SDL_sqrtf(radius * radius - dy * dy);
SDL_RenderDrawLineF(renderer, x - half_width, y + dy, x + half_width, y + dy);
}
}
/* Three corners, then the first corner again to close the shape. */
static void draw_triangle(float x, float y)
{
SDL_FPoint points[4] = {
{ x, y - RADIUS },
{ x + RADIUS, y + RADIUS },
{ x - RADIUS, y + RADIUS },
{ x, y - RADIUS }
};
SDL_RenderDrawLinesF(renderer, points, 4);
}
/* The graphics card fills a triangle from three corners, called vertices.
A color on each vertex blends across the shape. */
static void fill_triangle(float x, float y)
{
SDL_Vertex vertices[3] = {
{ { x, y - RADIUS }, { 255, 153, 90, 255 }, { 0.0f, 0.0f } },
{ { x + RADIUS, y + RADIUS }, { 38, 115, 217, 255 }, { 0.0f, 0.0f } },
{ { x - RADIUS, y + RADIUS }, { 76, 204, 140, 255 }, { 0.0f, 0.0f } }
};
SDL_RenderGeometry(renderer, NULL, vertices, 3, NULL, 0);
}
static void frame(void)
{
SDL_Event event;
SDL_FRect outline = { 180.0f, TOP_ROW - RADIUS, RADIUS * 2.0f, RADIUS * 2.0f };
SDL_FRect filled = { 180.0f, BOTTOM_ROW - RADIUS, RADIUS * 2.0f, RADIUS * 2.0f };
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) {
running = false;
}
}
SDL_SetRenderDrawColor(renderer, 24, 26, 32, SDL_ALPHA_OPAQUE);
SDL_RenderClear(renderer);
SDL_SetRenderDrawColor(renderer, 235, 236, 240, SDL_ALPHA_OPAQUE);
draw_zigzag(80.0f, TOP_ROW);
draw_dots(80.0f, BOTTOM_ROW);
SDL_SetRenderDrawColor(renderer, 255, 157, 92, SDL_ALPHA_OPAQUE);
SDL_RenderDrawRectF(renderer, &outline);
SDL_RenderFillRectF(renderer, &filled);
SDL_SetRenderDrawColor(renderer, 80, 200, 160, SDL_ALPHA_OPAQUE);
draw_circle(400.0f, TOP_ROW, RADIUS);
fill_circle(400.0f, BOTTOM_ROW, RADIUS);
SDL_SetRenderDrawColor(renderer, 120, 170, 240, SDL_ALPHA_OPAQUE);
draw_triangle(550.0f, TOP_ROW);
fill_triangle(550.0f, BOTTOM_ROW);
SDL_RenderPresent(renderer);
}
int main(int argc, char *argv[])
{
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
SDL_Log("SDL_Init failed: %s", SDL_GetError());
return 1;
}
if (SDL_CreateWindowAndRenderer(WINDOW_WIDTH, WINDOW_HEIGHT, 0, &window, &renderer) != 0) {
SDL_Log("SDL_CreateWindowAndRenderer failed: %s", SDL_GetError());
SDL_Quit();
return 1;
}
SDL_SetWindowTitle(window, "Draw shapes");
SDL_RenderSetVSync(renderer, 1);
#ifdef __EMSCRIPTEN__
emscripten_set_main_loop(frame, 0, 1);
#else
while (running) {
frame();
}
#endif
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}What SDL draws for you
The renderer has functions for points, lines, rectangles, and triangles. It has no function for a circle. This is normal in graphics programming. A screen is a grid of pixels, so a curve is only a set of pixels that you choose.
Points and lines
SDL_RenderPoint(renderer, x + dx, y + dy);SDL_RenderPoint colors one pixel. SDL_RenderLine draws one line between two points. SDL_RenderLines joins a list of points in order, so six points make five lines:
SDL_RenderDrawPointF(renderer, x + dx, y + dy);SDL_RenderDrawPointF colors one pixel. SDL_RenderDrawLineF draws one line between two points. SDL_RenderDrawLinesF joins a list of points in order, so six points make five lines:
SDL_RenderLines(renderer, points, 6); SDL_RenderDrawLinesF(renderer, points, 6);All lines are one pixel wide. For a thicker line, you draw a filled shape, or several lines side by side.
Outline and filled rectangles
SDL_RenderRect(renderer, &outline);
SDL_RenderFillRect(renderer, &filled); SDL_RenderDrawRectF(renderer, &outline);
SDL_RenderFillRectF(renderer, &filled);The two functions take the same rectangle. One draws its four edges, and the other fills it.
Draw a circle outline
Short straight lines look like a curve. So the program finds points around the circle and joins them:
points[i].x = x + radius * SDL_cosf(angle);
points[i].y = y + radius * SDL_sinf(angle);For any angle, cosine gives the distance across and sine gives the distance up or down, for a circle of radius 1. Multiply by the radius, add the center, and you have a point on the circle. The angle uses radians, so a full circle is two times pi.
The loop runs one extra time, and that last point is the same as the first. This closes the gap. More segments give a smoother circle, and 48 is plenty at this size.
Fill a circle
To fill a circle, the program draws one horizontal line for each row of pixels. Each row needs its own width:
float half_width = SDL_sqrtf(radius * radius - dy * dy);This is the Pythagorean theorem. The radius is the long side of a right triangle, dy is one short side, and half_width is the other. At the top of the circle the width is 0, and at the middle it is the full radius.
Triangles and vertices
An outline is three lines. The list has four points, because the last point returns to the first.
To fill a triangle, you use the function that the graphics card uses for almost everything. A vertex is a corner with its own position and color:
{ { x, y - RADIUS }, { 1.0f, 0.6f, 0.35f, 1.0f }, { 0.0f, 0.0f } },Each vertex holds a position, a color, and a texture position that this program does not use. In SDL3, the colors in a vertex go from 0.0 to 1.0.
SDL_RenderGeometry(renderer, NULL, vertices, 3, NULL, 0); { { x, y - RADIUS }, { 255, 153, 90, 255 }, { 0.0f, 0.0f } },Each vertex holds a position, a color, and a texture position that this program does not use. In SDL2, the colors in a vertex go from 0 to 255.
SDL_RenderGeometry(renderer, NULL, vertices, 3, NULL, 0);The graphics card fills the space between the three vertices. Each vertex has a different color, so the colors blend across the triangle. Nearly all 3D graphics are large numbers of small triangles that work in this same way.
Try this
- Change
CIRCLE_SEGMENTSto 6, then to 3. You get a hexagon, then a triangle. - Draw a clock face with 12 short lines around a circle.
- Draw a thick line. Use
fill_circlemany times along its length. - Make a filled rectangle from two triangles, with a different color on each corner.
What you learned
- SDL draws points, lines, rectangles, and triangles.
- A circle is many short lines, or one line for each row of pixels.
- A vertex is a corner with a position and a color.
- The graphics card fills triangles, and colors blend between the vertices.