Skip to content

File syn_canvas.c

File List > display > syn_canvas.c

Go to the documentation of this file

#if __has_include("syn_config.h")
#include "syn_config.h"
#endif

#if !defined(SYN_USE_CANVAS) || SYN_USE_CANVAS

#include "../util/syn_assert.h"
#include "../util/syn_qmath.h"
#include "syn_canvas.h"

#include <string.h>

/* ── Built-in 5×7 font (ASCII 32-126, column-major) ────────────────────── */

static const uint8_t font_5x7_data[] = {
    /* Each glyph is 5 bytes (5 columns × 7 rows packed into 5 bytes).
     * Bit 0 = top row, bit 6 = bottom row. */
    0x00, 0x00, 0x00, 0x00, 0x00, /* 32: space */
    0x00, 0x00, 0x5F, 0x00, 0x00, /* 33: ! */
    0x00, 0x07, 0x00, 0x07, 0x00, /* 34: " */
    0x14, 0x7F, 0x14, 0x7F, 0x14, /* 35: # */
    0x24, 0x2A, 0x7F, 0x2A, 0x12, /* 36: $ */
    0x23, 0x13, 0x08, 0x64, 0x62, /* 37: % */
    0x36, 0x49, 0x55, 0x22, 0x50, /* 38: & */
    0x00, 0x05, 0x03, 0x00, 0x00, /* 39: ' */
    0x00, 0x1C, 0x22, 0x41, 0x00, /* 40: ( */
    0x00, 0x41, 0x22, 0x1C, 0x00, /* 41: ) */
    0x08, 0x2A, 0x1C, 0x2A, 0x08, /* 42: * */
    0x08, 0x08, 0x3E, 0x08, 0x08, /* 43: + */
    0x00, 0x50, 0x30, 0x00, 0x00, /* 44: , */
    0x08, 0x08, 0x08, 0x08, 0x08, /* 45: - */
    0x00, 0x60, 0x60, 0x00, 0x00, /* 46: . */
    0x20, 0x10, 0x08, 0x04, 0x02, /* 47: / */
    0x3E, 0x51, 0x49, 0x45, 0x3E, /* 48: 0 */
    0x00, 0x42, 0x7F, 0x40, 0x00, /* 49: 1 */
    0x42, 0x61, 0x51, 0x49, 0x46, /* 50: 2 */
    0x21, 0x41, 0x45, 0x4B, 0x31, /* 51: 3 */
    0x18, 0x14, 0x12, 0x7F, 0x10, /* 52: 4 */
    0x27, 0x45, 0x45, 0x45, 0x39, /* 53: 5 */
    0x3C, 0x4A, 0x49, 0x49, 0x30, /* 54: 6 */
    0x01, 0x71, 0x09, 0x05, 0x03, /* 55: 7 */
    0x36, 0x49, 0x49, 0x49, 0x36, /* 56: 8 */
    0x06, 0x49, 0x49, 0x29, 0x1E, /* 57: 9 */
    0x00, 0x36, 0x36, 0x00, 0x00, /* 58: : */
    0x00, 0x56, 0x36, 0x00, 0x00, /* 59: ; */
    0x00, 0x08, 0x14, 0x22, 0x41, /* 60: < */
    0x14, 0x14, 0x14, 0x14, 0x14, /* 61: = */
    0x41, 0x22, 0x14, 0x08, 0x00, /* 62: > */
    0x02, 0x01, 0x51, 0x09, 0x06, /* 63: ? */
    0x32, 0x49, 0x79, 0x41, 0x3E, /* 64: @ */
    0x7E, 0x11, 0x11, 0x11, 0x7E, /* 65: A */
    0x7F, 0x49, 0x49, 0x49, 0x36, /* 66: B */
    0x3E, 0x41, 0x41, 0x41, 0x22, /* 67: C */
    0x7F, 0x41, 0x41, 0x22, 0x1C, /* 68: D */
    0x7F, 0x49, 0x49, 0x49, 0x41, /* 69: E */
    0x7F, 0x09, 0x09, 0x01, 0x01, /* 70: F */
    0x3E, 0x41, 0x41, 0x51, 0x32, /* 71: G */
    0x7F, 0x08, 0x08, 0x08, 0x7F, /* 72: H */
    0x00, 0x41, 0x7F, 0x41, 0x00, /* 73: I */
    0x20, 0x40, 0x41, 0x3F, 0x01, /* 74: J */
    0x7F, 0x08, 0x14, 0x22, 0x41, /* 75: K */
    0x7F, 0x40, 0x40, 0x40, 0x40, /* 76: L */
    0x7F, 0x02, 0x04, 0x02, 0x7F, /* 77: M */
    0x7F, 0x04, 0x08, 0x10, 0x7F, /* 78: N */
    0x3E, 0x41, 0x41, 0x41, 0x3E, /* 79: O */
    0x7F, 0x09, 0x09, 0x09, 0x06, /* 80: P */
    0x3E, 0x41, 0x51, 0x21, 0x5E, /* 81: Q */
    0x7F, 0x09, 0x19, 0x29, 0x46, /* 82: R */
    0x46, 0x49, 0x49, 0x49, 0x31, /* 83: S */
    0x01, 0x01, 0x7F, 0x01, 0x01, /* 84: T */
    0x3F, 0x40, 0x40, 0x40, 0x3F, /* 85: U */
    0x1F, 0x20, 0x40, 0x20, 0x1F, /* 86: V */
    0x7F, 0x20, 0x18, 0x20, 0x7F, /* 87: W */
    0x63, 0x14, 0x08, 0x14, 0x63, /* 88: X */
    0x03, 0x04, 0x78, 0x04, 0x03, /* 89: Y */
    0x61, 0x51, 0x49, 0x45, 0x43, /* 90: Z */
    0x00, 0x00, 0x7F, 0x41, 0x41, /* 91: [ */
    0x02, 0x04, 0x08, 0x10, 0x20, /* 92: backslash */
    0x41, 0x41, 0x7F, 0x00, 0x00, /* 93: ] */
    0x04, 0x02, 0x01, 0x02, 0x04, /* 94: ^ */
    0x40, 0x40, 0x40, 0x40, 0x40, /* 95: _ */
    0x00, 0x01, 0x02, 0x04, 0x00, /* 96: ` */
    0x20, 0x54, 0x54, 0x54, 0x78, /* 97: a */
    0x7F, 0x48, 0x44, 0x44, 0x38, /* 98: b */
    0x38, 0x44, 0x44, 0x44, 0x20, /* 99: c */
    0x38, 0x44, 0x44, 0x48, 0x7F, /* 100: d */
    0x38, 0x54, 0x54, 0x54, 0x18, /* 101: e */
    0x08, 0x7E, 0x09, 0x01, 0x02, /* 102: f */
    0x08, 0x14, 0x54, 0x54, 0x3C, /* 103: g */
    0x7F, 0x08, 0x04, 0x04, 0x78, /* 104: h */
    0x00, 0x44, 0x7D, 0x40, 0x00, /* 105: i */
    0x20, 0x40, 0x44, 0x3D, 0x00, /* 106: j */
    0x00, 0x7F, 0x10, 0x28, 0x44, /* 107: k */
    0x00, 0x41, 0x7F, 0x40, 0x00, /* 108: l */
    0x7C, 0x04, 0x18, 0x04, 0x78, /* 109: m */
    0x7C, 0x08, 0x04, 0x04, 0x78, /* 110: n */
    0x38, 0x44, 0x44, 0x44, 0x38, /* 111: o */
    0x7C, 0x14, 0x14, 0x14, 0x08, /* 112: p */
    0x08, 0x14, 0x14, 0x18, 0x7C, /* 113: q */
    0x7C, 0x08, 0x04, 0x04, 0x08, /* 114: r */
    0x48, 0x54, 0x54, 0x54, 0x20, /* 115: s */
    0x04, 0x3F, 0x44, 0x40, 0x20, /* 116: t */
    0x3C, 0x40, 0x40, 0x20, 0x7C, /* 117: u */
    0x1C, 0x20, 0x40, 0x20, 0x1C, /* 118: v */
    0x3C, 0x40, 0x30, 0x40, 0x3C, /* 119: w */
    0x44, 0x28, 0x10, 0x28, 0x44, /* 120: x */
    0x0C, 0x50, 0x50, 0x50, 0x3C, /* 121: y */
    0x44, 0x64, 0x54, 0x4C, 0x44, /* 122: z */
    0x00, 0x08, 0x36, 0x41, 0x00, /* 123: { */
    0x00, 0x00, 0x7F, 0x00, 0x00, /* 124: | */
    0x00, 0x41, 0x36, 0x08, 0x00, /* 125: } */
    0x08, 0x08, 0x2A, 0x1C, 0x08, /* 126: ~ */
};

const SYN_Font syn_font_5x7 = {
    .data = font_5x7_data,
    .width = 5,
    .height = 7,
    .first_char = 32,
    .char_count = 95,
};

/* ── Init ───────────────────────────────────────────────────────────────── */

void syn_canvas_init(SYN_Canvas *c, uint8_t *buf, uint16_t w, uint16_t h, uint8_t bpp,
                     SYN_Canvas_FlushFn flush, void *ctx)
{
    SYN_ASSERT(c != NULL);
    SYN_ASSERT(buf != NULL);
    SYN_ASSERT(bpp == 1 || bpp == 16);

    memset(c, 0, sizeof(*c));
    c->framebuf = buf;
    c->width = w;
    c->height = h;
    c->bpp = bpp;
    c->font = &syn_font_5x7;
    c->flush_fn = flush;
    c->flush_ctx = ctx;
    c->clip_x = 0;
    c->clip_y = 0;
    c->clip_w = (int16_t)w;
    c->clip_h = (int16_t)h;

    if (bpp == 1) {
        c->buf_size = (size_t)((w + 7) / 8) * h;
    } else {
        c->buf_size = (size_t)w * h * 2;
    }
}

void syn_canvas_set_clip(SYN_Canvas *c, int16_t x, int16_t y, int16_t w, int16_t h)
{
    SYN_ASSERT(c != NULL);
    c->clip_x = x;
    c->clip_y = y;
    c->clip_w = w;
    c->clip_h = h;
}

void syn_canvas_reset_clip(SYN_Canvas *c)
{
    SYN_ASSERT(c != NULL);
    c->clip_x = 0;
    c->clip_y = 0;
    c->clip_w = (int16_t)c->width;
    c->clip_h = (int16_t)c->height;
}

void syn_canvas_set_font(SYN_Canvas *c, const SYN_Font *font)
{
    SYN_ASSERT(c != NULL);
    c->font = (font != NULL) ? font : &syn_font_5x7;
}

void syn_canvas_clear(SYN_Canvas *c)
{
    SYN_ASSERT(c != NULL);
    memset(c->framebuf, 0, c->buf_size);
}

void syn_canvas_fill(SYN_Canvas *c, uint16_t color)
{
    SYN_ASSERT(c != NULL);
    if (c->bpp == 1) {
        memset(c->framebuf, color ? 0xFF : 0x00, c->buf_size);
    } else {
        /* RGB565: fill with 16-bit color */
        uint8_t hi = (uint8_t)(color >> 8);
        uint8_t lo = (uint8_t)(color);
        for (size_t i = 0; i < c->buf_size; i += 2) {
            c->framebuf[i] = hi;
            c->framebuf[i + 1] = lo;
        }
    }
}

/* ── Pixel ──────────────────────────────────────────────────────────────── */

void syn_canvas_pixel(SYN_Canvas *c, int16_t x, int16_t y, uint16_t color)
{
    if (x < c->clip_x || x >= c->clip_x + c->clip_w)
        return;
    if (y < c->clip_y || y >= c->clip_y + c->clip_h)
        return;

    if (c->bpp == 1) {
        /* Mono: column-major page layout (SSD1306 style).
         * Each byte is a vertical 8-pixel column.
         * Page = y / 8, bit = y % 8. */
        size_t page = (size_t)y / 8;
        size_t idx = page * c->width + (size_t)x;
        uint8_t bit = (uint8_t)(1 << (y % 8));
        if (color) {
            c->framebuf[idx] |= bit;
        } else {
            c->framebuf[idx] &= ~bit;
        }
    } else {
        /* RGB565: row-major, 2 bytes per pixel */
        size_t idx = ((size_t)y * c->width + (size_t)x) * 2;
        c->framebuf[idx] = (uint8_t)(color >> 8);
        c->framebuf[idx + 1] = (uint8_t)(color);
    }
}

/* ── Line (Bresenham) ───────────────────────────────────────────────────── */

void syn_canvas_line(SYN_Canvas *c, int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint16_t color)
{
    int16_t dx = (x1 > x0) ? (x1 - x0) : (x0 - x1);
    int16_t dy = (y1 > y0) ? (y1 - y0) : (y0 - y1);
    int16_t sx = (x0 < x1) ? 1 : -1;
    int16_t sy = (y0 < y1) ? 1 : -1;
    int16_t err = dx - dy;

    for (;;) {
        syn_canvas_pixel(c, x0, y0, color);
        if (x0 == x1 && y0 == y1)
            break;
        int16_t e2 = err * 2;
        if (e2 > -dy) {
            err -= dy;
            x0 += sx;
        }
        if (e2 < dx) {
            err += dx;
            y0 += sy;
        }
    }
}

void syn_canvas_line_aa(SYN_Canvas *c, int16_t x0, int16_t y0, int16_t x1, int16_t y1,
                        uint16_t color)
{
    syn_canvas_line(c, x0, y0, x1, y1, color);
}

/* ── Rectangle ──────────────────────────────────────────────────────────── */

void syn_canvas_rect(SYN_Canvas *c, int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color)
{
    syn_canvas_line(c, x, y, x + w - 1, y, color);
    syn_canvas_line(c, x + w - 1, y, x + w - 1, y + h - 1, color);
    syn_canvas_line(c, x + w - 1, y + h - 1, x, y + h - 1, color);
    syn_canvas_line(c, x, y + h - 1, x, y, color);
}

void syn_canvas_rect_fill(SYN_Canvas *c, int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color)
{
    for (int16_t row = y; row < y + h; row++) {
        for (int16_t col = x; col < x + w; col++) {
            syn_canvas_pixel(c, col, row, color);
        }
    }
}

/* ── Circle (Bresenham midpoint) ────────────────────────────────────────── */

void syn_canvas_circle(SYN_Canvas *c, int16_t cx, int16_t cy, int16_t r, uint16_t color)
{
    int16_t x = r;
    int16_t y = 0;
    int16_t err = 1 - r;

    while (x >= y) {
        syn_canvas_pixel(c, cx + x, cy + y, color);
        syn_canvas_pixel(c, cx + y, cy + x, color);
        syn_canvas_pixel(c, cx - y, cy + x, color);
        syn_canvas_pixel(c, cx - x, cy + y, color);
        syn_canvas_pixel(c, cx - x, cy - y, color);
        syn_canvas_pixel(c, cx - y, cy - x, color);
        syn_canvas_pixel(c, cx + y, cy - x, color);
        syn_canvas_pixel(c, cx + x, cy - y, color);
        y++;
        if (err < 0) {
            err += 2 * y + 1;
        } else {
            x--;
            err += 2 * (y - x) + 1;
        }
    }
}

/* ── Filled Circle ──────────────────────────────────────────────────────── */

void syn_canvas_circle_fill(SYN_Canvas *c, int16_t cx, int16_t cy, int16_t r, uint16_t color)
{
    SYN_ASSERT(c != NULL);
    int16_t x = r;
    int16_t y = 0;
    int16_t err = 1 - r;

    while (x >= y) {
        for (int16_t i = cx - x; i <= cx + x; i++) {
            syn_canvas_pixel(c, i, cy + y, color);
            syn_canvas_pixel(c, i, cy - y, color);
        }
        for (int16_t i = cx - y; i <= cx + y; i++) {
            syn_canvas_pixel(c, i, cy + x, color);
            syn_canvas_pixel(c, i, cy - x, color);
        }
        y++;
        if (err < 0) {
            err += 2 * y + 1;
        } else {
            x--;
            err += 2 * (y - x) + 1;
        }
    }
}

/* ── Rounded Rectangle ──────────────────────────────────────────────────── */

static void draw_rounded_corners(SYN_Canvas *c, int16_t x, int16_t y, int16_t w, int16_t h,
                                 int16_t r, uint16_t color)
{
    int16_t x_tr = x + w - 1 - r, y_tr = y + r;
    int16_t x_tl = x + r, y_tl = y + r;
    int16_t x_bl = x + r, y_bl = y + h - 1 - r;
    int16_t x_br = x + w - 1 - r, y_br = y + h - 1 - r;

    int16_t px = r;
    int16_t py = 0;
    int16_t err = 1 - r;

    while (px >= py) {
        /* Top-Right */
        syn_canvas_pixel(c, x_tr + px, y_tr - py, color);
        syn_canvas_pixel(c, x_tr + py, y_tr - px, color);
        /* Top-Left */
        syn_canvas_pixel(c, x_tl - px, y_tl - py, color);
        syn_canvas_pixel(c, x_tl - py, y_tl - px, color);
        /* Bottom-Left */
        syn_canvas_pixel(c, x_bl - px, y_bl + py, color);
        syn_canvas_pixel(c, x_bl - py, y_bl + px, color);
        /* Bottom-Right */
        syn_canvas_pixel(c, x_br + px, y_br + py, color);
        syn_canvas_pixel(c, x_br + py, y_br + px, color);

        py++;
        if (err < 0) {
            err += 2 * py + 1;
        } else {
            px--;
            err += 2 * (py - px) + 1;
        }
    }
}

void syn_canvas_rect_round(SYN_Canvas *c, int16_t x, int16_t y, int16_t w, int16_t h, int16_t r,
                           uint16_t color)
{
    SYN_ASSERT(c != NULL);
    if (w <= 0 || h <= 0)
        return;

    if (r > w / 2)
        r = w / 2;
    if (r > h / 2)
        r = h / 2;
    if (r <= 0) {
        syn_canvas_rect(c, x, y, w, h, color);
        return;
    }

    /* Draw straight sides */
    syn_canvas_line(c, x + r, y, x + w - 1 - r, y, color);
    syn_canvas_line(c, x + r, y + h - 1, x + w - 1 - r, y + h - 1, color);
    syn_canvas_line(c, x, y + r, x, y + h - 1 - r, color);
    syn_canvas_line(c, x + w - 1, y + r, x + w - 1, y + h - 1 - r, color);

    /* Draw corners */
    draw_rounded_corners(c, x, y, w, h, r, color);
}

void syn_canvas_rect_round_fill(SYN_Canvas *c, int16_t x, int16_t y, int16_t w, int16_t h,
                                int16_t r, uint16_t color)
{
    SYN_ASSERT(c != NULL);
    if (w <= 0 || h <= 0)
        return;

    if (r > w / 2)
        r = w / 2;
    if (r > h / 2)
        r = h / 2;
    if (r <= 0) {
        syn_canvas_rect_fill(c, x, y, w, h, color);
        return;
    }

    /* 1. Draw the middle body (full width rows) */
    for (int16_t row = y + r; row < y + h - r; row++) {
        for (int16_t col = x; col < x + w; col++) {
            syn_canvas_pixel(c, col, row, color);
        }
    }

    /* 2. Draw the rounded top/bottom parts scanline by scanline using Bresenham */
    int16_t px = r;
    int16_t py = 0;
    int16_t err = 1 - r;

    while (px >= py) {
        /* top py */
        for (int16_t col = x + r - px; col <= x + w - 1 - r + px; col++) {
            syn_canvas_pixel(c, col, y + r - py - 1, color);
        }
        /* top px */
        for (int16_t col = x + r - py; col <= x + w - 1 - r + py; col++) {
            syn_canvas_pixel(c, col, y + r - px - 1, color);
        }
        /* bottom py */
        for (int16_t col = x + r - px; col <= x + w - 1 - r + px; col++) {
            syn_canvas_pixel(c, col, y + h - r + py, color);
        }
        /* bottom px */
        for (int16_t col = x + r - py; col <= x + w - 1 - r + py; col++) {
            syn_canvas_pixel(c, col, y + h - r + px, color);
        }

        py++;
        if (err < 0) {
            err += 2 * py + 1;
        } else {
            px--;
            err += 2 * (py - px) + 1;
        }
    }
}

/* ── Bitmap ─────────────────────────────────────────────────────────────── */

void syn_canvas_bitmap(SYN_Canvas *c, int16_t x, int16_t y, const uint8_t *bitmap, int16_t w,
                       int16_t h, uint16_t color)
{
    SYN_ASSERT(c != NULL);
    SYN_ASSERT(bitmap != NULL);
    if (w <= 0 || h <= 0)
        return;

    int16_t bytes_per_row = (w + 7) / 8;

    for (int16_t row = 0; row < h; row++) {
        for (int16_t col = 0; col < w; col++) {
            int16_t byte_idx = row * bytes_per_row + col / 8;
            uint8_t bit_mask = (uint8_t)(1 << (7 - (col % 8)));
            if (bitmap[byte_idx] & bit_mask) {
                syn_canvas_pixel(c, x + col, y + row, color);
            }
        }
    }
}

/* ── Text ───────────────────────────────────────────────────────────────── */

uint8_t syn_canvas_char(SYN_Canvas *c, int16_t x, int16_t y, char ch, uint16_t color)
{
    const SYN_Font *f = c->font;
    if (f == NULL)
        return 0;

    uint8_t idx = (uint8_t)ch - f->first_char;
    if (idx >= f->char_count)
        return f->width + 1;

    const uint8_t *glyph = &f->data[(size_t)idx * f->width];

    for (uint8_t col = 0; col < f->width; col++) {
        uint8_t column = glyph[col];
        for (uint8_t row = 0; row < f->height; row++) {
            if (column & (1 << row)) {
                syn_canvas_pixel(c, x + col, y + row, color);
            }
        }
    }

    return f->width + 1; /* advance = glyph width + 1px spacing */
}

void syn_canvas_text(SYN_Canvas *c, int16_t x, int16_t y, const char *str, uint16_t color)
{
    SYN_ASSERT(c != NULL);
    if (str == NULL)
        return;

    while (*str) {
        x += (int16_t)syn_canvas_char(c, x, y, *str, color);
        str++;
    }
}

uint16_t syn_canvas_text_width(const SYN_Canvas *c, const char *str)
{
    SYN_ASSERT(c != NULL);
    if (str == NULL || c->font == NULL)
        return 0;

    uint16_t w = 0;
    while (*str) {
        w += c->font->width + 1;
        str++;
    }
    return (w > 0) ? w - 1 : 0; /* remove trailing spacing */
}

uint8_t syn_canvas_text_height(const SYN_Canvas *c)
{
    SYN_ASSERT(c != NULL);
    if (c->font == NULL)
        return 0;
    return c->font->height;
}

/* ── Fast horizontal / vertical lines ───────────────────────────────────── */

void syn_canvas_hline(SYN_Canvas *c, int16_t x, int16_t y, int16_t w, uint16_t color)
{
    int16_t i;
    for (i = 0; i < w; i++) {
        syn_canvas_pixel(c, x + i, y, color);
    }
}

void syn_canvas_vline(SYN_Canvas *c, int16_t x, int16_t y, int16_t h, uint16_t color)
{
    int16_t i;
    for (i = 0; i < h; i++) {
        syn_canvas_pixel(c, x, y + i, color);
    }
}

/* ── Flush ──────────────────────────────────────────────────────────────── */

void syn_canvas_flush(SYN_Canvas *c)
{
    SYN_ASSERT(c != NULL);
    if (c->flush_fn != NULL) {
        c->flush_fn(c->framebuf, c->buf_size, c->flush_ctx);
    }
}

void syn_canvas_flush_partial(SYN_Canvas *c, size_t offset, size_t len)
{
    SYN_ASSERT(c != NULL);
    if (c->flush_fn == NULL)
        return;
    if (offset >= c->buf_size)
        return;
    /* Clamp to buffer bounds */
    if (len > c->buf_size - offset) {
        len = c->buf_size - offset;
    }
    c->flush_fn(c->framebuf + offset, len, c->flush_ctx);
}

void syn_canvas_line_polar(SYN_Canvas *c, int16_t cx, int16_t cy, int16_t angle_deg, int16_t length,
                           uint16_t color)
{
    SYN_ASSERT(c != NULL);
    if (length <= 0)
        return;

    int32_t a = angle_deg % 360;
    if (a < 0)
        a += 360;

    q16_t rad = q16_mul((q16_t)(a << 16), Q16_PI) / 180;
    q16_t cos_val = q16_cos(rad);
    q16_t sin_val = q16_sin(rad);

    int16_t x2 = (int16_t)(cx + (q16_mul((q16_t)length << 16, cos_val) >> 16));
    int16_t y2 = (int16_t)(cy + (q16_mul((q16_t)length << 16, sin_val) >> 16));

    syn_canvas_line(c, cx, cy, x2, y2, color);
}

void syn_canvas_arc(SYN_Canvas *c, int16_t cx, int16_t cy, int16_t r, int16_t start_angle_deg,
                    int16_t end_angle_deg, uint16_t color)
{
    SYN_ASSERT(c != NULL);
    if (r <= 0)
        return;

    int16_t s = start_angle_deg;
    int16_t e = end_angle_deg;
    if (s > e) {
        int16_t tmp = s;
        s = e;
        e = tmp;
    }

    for (int16_t a = s; a < e; a += 2) {
        int16_t next_a = (a + 2 > e) ? e : (int16_t)(a + 2);
        int32_t norm_a1 = a % 360;
        if (norm_a1 < 0)
            norm_a1 += 360;
        int32_t norm_a2 = next_a % 360;
        if (norm_a2 < 0)
            norm_a2 += 360;

        q16_t rad1 = q16_mul((q16_t)(norm_a1 << 16), Q16_PI) / 180;
        q16_t rad2 = q16_mul((q16_t)(norm_a2 << 16), Q16_PI) / 180;

        int16_t x1 = (int16_t)(cx + (q16_mul((q16_t)r << 16, q16_cos(rad1)) >> 16));
        int16_t y1 = (int16_t)(cy + (q16_mul((q16_t)r << 16, q16_sin(rad1)) >> 16));
        int16_t x2 = (int16_t)(cx + (q16_mul((q16_t)r << 16, q16_cos(rad2)) >> 16));
        int16_t y2 = (int16_t)(cy + (q16_mul((q16_t)r << 16, q16_sin(rad2)) >> 16));

        syn_canvas_line(c, x1, y1, x2, y2, color);
    }
}

#endif /* SYN_USE_CANVAS */