btprnt

btprnt

A small library for drawing bitmap images.

Files

btprnt writes itself to a single header file called btprnt.h. Defining BTPRNT_IMPL will define the functions, otherwise it is just function declarations.

<<btprnt.h>>=
#ifndef BTPRNT_H
#define BTPRNT_H
<<typedefs>>
<<btprnt_region_struct>>
<<funcdefs>>
#ifdef BTPRNT_IMPL
#include <math.h>
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
<<structs>>
<<funcs>>
#endif
#endif

The Top Level Interface

The btprnt interface is one that handles all the low-level details.

<<typedefs>>=
typedef struct btprnt btprnt;

The btprnt struct contains a canvas, and a buffer.

<<structs>>=
struct btprnt {
    btprnt_buf *buf;
    btprnt_canvas *canvas;
};

Canvas can be retrieved using the function btprnt_canvas_get.

<<funcdefs>>=
btprnt_canvas *btprnt_canvas_get(btprnt *b);
<<funcs>>=
btprnt_canvas *btprnt_canvas_get(btprnt *b)
{
    return b->canvas;
}

The buffer can be retrieved using btprnt_buf_get.

<<funcdefs>>=
btprnt_buf * btprnt_buf_get(btprnt *b);
<<funcs>>=
btprnt_buf * btprnt_buf_get(btprnt *b)
{
    return b->buf;
}

The function btprnt_new will allocate and return a new btprnt instance. Internally, this will allocate and initialize the canvas and the buffer. The dimensions of the buffer are needed to be known at init time.

<<funcdefs>>=
btprnt * btprnt_new(int w, int h);

<<funcs>>=
btprnt * btprnt_new(int w, int h)
{
    btprnt *b;

    b = calloc(1, sizeof(btprnt));

    if (b == NULL) return NULL;

    b->buf = btprnt_buf_init(w, h);

    if (b->buf == NULL) {
        free(b);
        return NULL;
    }

    b->canvas = btprnt_canvas_new(b->buf);

    if (b->canvas == NULL) {
        btprnt_buf_free(&b->buf);
        free(b);
        return NULL;
    }
    return b;
}

The function btprnt_del will delete that which has been previously allocated.

<<funcdefs>>=
void btprnt_del(btprnt **b);

This pointer notation is a bit too clever for my peabrain, but it is very convenient. I will explain it while it is still fresh in my head:

b is a pointer to a pointer to btprnt (aka double star)

*b will return the pointer to btprnt. We need this to access things like canvas with (*b)->canvas.

The freeing functions take pointers to pointers (double stars), so we need to give it the address of the pointer &(*b)->canvas, NOT the pointer itself which would be (*b)->canvas.

<<funcs>>=
void btprnt_del(btprnt **b)
{
    if (*b == NULL) return;
    btprnt_canvas_del(&(*b)->canvas);
    btprnt_buf_free(&(*b)->buf);
    free(*b);
    *b = NULL;
}

<<funcdefs>>=
void btprnt_del(btprnt **b);

The Buffer

The lowest level data construct is a buffer. This is where bits are written to.

<<typedefs>>=
typedef struct btprnt_buf btprnt_buf;

A bitmap has an array where it stores data, and integers storing the width and a height.

<<structs>>=
struct btprnt_buf {
    int w;
    int h;
    int stride;
    unsigned char free;
    unsigned char *data;
};

The buffer is allocated + initialized with the function btprnt_buf_init.

<<funcdefs>>=
btprnt_buf * btprnt_buf_init(int w, int h);

To make the math a bit easier, the rows will be rounded to the nearest multiple of 8.

<<funcs>>=
btprnt_buf * btprnt_buf_init(int w, int h)
{
    btprnt_buf *b;
    int stride;

    b = calloc(1, sizeof(btprnt_buf));

    if (b == NULL) return NULL;

    b->free = 1;
    b->w = w;
    b->h = h;

    if (w % 8) {
        stride = ((w / 8) + 1) * 8;
    } else {
        stride = w / 8;
    }

    b->stride = stride;
    b->data = calloc(1, stride * h);

    if (b->data == NULL) {
        free(b);
        return NULL;
    }

    return b;
}

Buffers that use externally managed memory blocks can be created with btprnt_buf_extmem

<<funcdefs>>=
btprnt_buf * btprnt_buf_extmem(int w, int h,
                               unsigned char *data);

<<funcs>>=
btprnt_buf * btprnt_buf_extmem(int w, int h,
                               unsigned char *data)
{
    btprnt_buf *b;
    int stride;

    b = calloc(1, sizeof(btprnt_buf));

    if (b == NULL) return NULL;

    b->free = 0;
    b->w = w;
    b->h = h;

    if (w % 8) {
        stride = ((w / 8) + 1) * 8;
    } else {
        stride = w / 8;
    }

    b->stride = stride;
    b->data = data;

    return b;
}

The buffer is freed with btprnt_buf_free.

<<funcdefs>>=
void btprnt_buf_free(btprnt_buf **buf);

The data and the struct must be freed. To prevent double-free corruptions, the pointer is set to be NULL.

<<funcs>>=
void btprnt_buf_free(btprnt_buf **buf)
{
    if (*buf == NULL) return;
    if ((*buf)->free) free((*buf)->data);
    free(*buf);
    *buf = NULL;
}


The main operations are read + write with btprnt_buf_read and btprnt_buf_write.

<<funcdefs>>=
unsigned char btprnt_buf_read(btprnt_buf *b, int x, int y);
void btprnt_buf_write(btprnt_buf *b, int x, int y, int c);

Reading bit is a matter of first finding the byte where it is located, and then ANDing with the local bit location.

Multiplying the stride by the height gives us which to look at. Adding x divided by 8 (bits to a byte), gives us the byte offset.

No coordinate checks done here so be careful. This isn't an interface to be accessed directly. Sanitized inputs are expected to be here.

<<funcs>>=
unsigned char btprnt_buf_read(btprnt_buf *b, int x, int y)
{
    unsigned char bitpos;
    int pos;
    int off;

    off = x >> 3;
    pos = (y * b->stride) + off;
    bitpos = x - (off * 8);

    return (b->data[pos] & (1 << bitpos)) > 0;
}

Similar process with reading, except the buffer is toggled on or off depending on the value of c.

<<funcs>>=
void btprnt_buf_write(btprnt_buf *b, int x, int y, int c)
{
    unsigned char bitpos;
    int pos;
    int off;

    off = x >> 3;
    pos = (y * b->stride) + off;
    bitpos = x - (off * 8);

    if (c) {
        b->data[pos] |= (1 << bitpos);
    } else {
        b->data[pos] &= ~(1 << bitpos);
    }
}

Dimensions for the buffer can be retrieved using btprnt_buf_width and btprnt_buf_height.

<<funcdefs>>=
int btprnt_buf_width(btprnt_buf *buf);
int btprnt_buf_height(btprnt_buf *buf);

<<funcs>>=
int btprnt_buf_width(btprnt_buf *buf)
{
    return buf->w;
}

int btprnt_buf_height(btprnt_buf *buf)
{
    return buf->h;
}

A buffer can be written to a pbm file with the function btprnt_buf_pbm.

<<funcdefs>>=
void btprnt_buf_pbm(btprnt_buf *buf, const char *filename);
<<funcs>>=
void btprnt_buf_pbm(btprnt_buf *buf, const char *filename)
{
    FILE *fp;
    int x, y;
    int count;
    fp = fopen(filename, "w");

    if (buf == NULL || fp == NULL) return;

    fprintf(fp, "P1\n");
    fprintf(fp, "# Generated with btprnt\n");
    fprintf(fp, "%d %d\n", buf->w, buf->h);

    count = 0;
    for(y = 0; y < buf->h; y++) {
        for(x = 0; x < buf->w; x++) {
            fprintf(fp, "%d", btprnt_buf_read(buf, x, y));
            count++;
            if (count == 16) {
                count = 0;
                fprintf(fp, "\n");
            } else if (count != 0) {
                fprintf(fp, " ");
            }
        }
    }

    fclose(fp);
}

The buffer can also be written to an XBM file using the function btprnt_buf_xbm.

<<funcdefs>>=
void btprnt_buf_xbm(btprnt_buf *buf,
                    const char *name,
                    const char *filename);
<<funcs>>=
void btprnt_buf_xbm(btprnt_buf *buf,
                    const char *name,
                    const char *filename)
{
    FILE *fp;
    int n;
    unsigned int count;

    fp = fopen(filename, "w");

    if (buf == NULL || fp == NULL) return;

    fprintf(fp, "#define %s_width %d\n", name, buf->w);
    fprintf(fp, "#define %s_height %d\n", name, buf->h);
    fprintf(fp, "static unsigned char %s_bits[] = {\n", name);

    count = buf->h * buf->stride;

    for (n = 0; n < count; n++) {
        fprintf(fp, "0x%x,", buf->data[n]);
        if ((n + 1) % 8 == 0) {
            fprintf(fp, "\n");
        } else {
            fprintf(fp, " ");
        }
    }

    fprintf(fp, "};");


    fclose(fp);
}

The Region

Regions are rectangular spaces to draw stuff into.

<<typedefs>>=
typedef struct btprnt_region btprnt_region;

A region provides two main things: a local coordinate space, and rectangular clipping.

A region contains the top left coordinate position, the region width, and the region height.

Because of how important regions for user level operations, this struct is actually exposed in the public header, allowing for instances to be allocated on the stack instead of the heap.

<<btprnt_region_struct>>=
struct btprnt_region {
    btprnt_canvas *c;
    int w, h;
    int x, y;
};

A new region is created with btprnt_region_new.

<<funcdefs>>=
btprnt_region * btprnt_region_new(btprnt_canvas *c,
                                  int x, int y,
                                  int w, int h);

<<funcs>>=
btprnt_region * btprnt_region_new(btprnt_canvas *c,
                                  int x, int y,
                                  int w, int h)
{
    btprnt_region *r;

    r = calloc(1, sizeof(btprnt_region));

    if (r == NULL) return NULL;

    btprnt_region_init(c, r, x, y, w, h);

    return r;
}

It is freed with btprnt_region_del.

<<funcdefs>>=
void btprnt_region_del(btprnt_region **r);

<<funcs>>=
void btprnt_region_del(btprnt_region **r)
{
    if (*r == NULL) return;
    free(*r);
    *r = NULL;
}

If a region is to be allocated on the stack, it needs only to be initialized. This can be done with btprnt_region_init.

<<funcdefs>>=
void btprnt_region_init(btprnt_canvas *c,
                        btprnt_region *r,
                        int x, int y,
                        int w, int h);
<<funcs>>=
void btprnt_region_init(btprnt_canvas *c,
                        btprnt_region *r,
                        int x, int y,
                        int w, int h)
{
    btprnt_region_xpos_set(r, x);
    btprnt_region_ypos_set(r, y);
    btprnt_region_width_set(r, w);
    btprnt_region_height_set(r, h);
    r->c = c;
}

The dimensions and position can be changed after it is instantiated.

<<funcdefs>>=
void btprnt_region_xpos_set(btprnt_region *r, int x);
void btprnt_region_ypos_set(btprnt_region *r, int y);
void btprnt_region_width_set(btprnt_region *r, int w);
void btprnt_region_height_set(btprnt_region *r, int h);

<<funcs>>=
void btprnt_region_xpos_set(btprnt_region *r, int x)
{
    r->x = x;
}

void btprnt_region_ypos_set(btprnt_region *r, int y)
{
    r->y = y;
}

void btprnt_region_width_set(btprnt_region *r, int w)
{
    r->w = w;
}

void btprnt_region_height_set(btprnt_region *r, int h)
{
    r->h = h;
}

Drawing utilities write to a region. The region ensures that the pixel is not being written beyond the bounds of itself. The actual placement on the buffer is handled via a canvas.

<<funcdefs>>=
void btprnt_region_draw(btprnt_region *r,
                        int x, int y,
                        int c);

For now, this function will be a little bit overpowered. In the future, it might be better to break this up into different components and flesh out the canvas interface.

<<funcs>>=
void btprnt_region_draw(btprnt_region *r,
                        int x, int y,
                        int c)
{
    btprnt_canvas *cv;
    int gx, gy;

    cv = r->c;

    if (x < 0 || x >= r->w) return;
    if (y < 0 || y >= r->h) return;

    gx = cv->offx + r->x + x;
    if (gx < 0 || gx >= cv->buf->w) return;
    gy = cv->offy + r->y + y;
    if (gy < 0 || gy >= cv->buf->h) return;

    btprnt_buf_write(cv->buf, gx, gy, c);
}

The Canvas

The canvas is an abstraction of the bitmap buffer. Regions get drawn to the buffer via a canvas.

<<typedefs>>=
typedef struct btprnt_canvas btprnt_canvas;

It can be created with btprnt_canvas_new, and freed with btprnt_canvas_del.

<<funcdefs>>=
btprnt_canvas * btprnt_canvas_new(btprnt_buf *buf);

<<funcs>>=
btprnt_canvas * btprnt_canvas_new(btprnt_buf *buf)
{
    btprnt_canvas *c;

    c = calloc(1, sizeof(btprnt_canvas));

    if (c == NULL) return NULL;

    c->buf = buf;
    btprnt_canvas_offx_set(c, 0);
    btprnt_canvas_offy_set(c, 0);
    return c;
}

<<funcdefs>>=
void btprnt_canvas_offx_set(btprnt_canvas *c, int x);
void btprnt_canvas_offy_set(btprnt_canvas *c, int y);

<<funcs>>=
void btprnt_canvas_offx_set(btprnt_canvas *c, int x)
{
    c->offx = x;
}

void btprnt_canvas_offy_set(btprnt_canvas *c, int y)
{
    c->offy = y;
}

<<funcdefs>>=
void btprnt_canvas_del(btprnt_canvas **c);

<<funcs>>=
void btprnt_canvas_del(btprnt_canvas **c)
{
    if (*c == NULL) return;
    free(*c);
    *c = NULL;
}

The main point of canvas abstraction is to provide an infinite for regions to lie on. Any pixels out of range of the buffer will be clipped by the canvas. Regions can be resized and moved around without having to worry about accessing bad memory.

<<structs>>=
struct btprnt_canvas {
    btprnt_buf *buf;
    int offx, offy;
};

The canvas has a global offset value. A canvas can be moved around to make regions or less visible.

Text

Text is next most important thing after all the fundamentals. This can be broken up into levels.

Level 1: Drawing tiles from a map

The lowest layer involves drawing a single tile from a tile map stored in memory.

<<funcdefs>>=
void btprnt_draw_tile(btprnt_region *reg,
                      btprnt_buf *map,
                      int xpos, int ypos,
                      int mx, int my,
                      int w, int h,
                      int scale, int color);

<<funcs>>=
void btprnt_draw_tile(btprnt_region *reg,
                      btprnt_buf *map,
                      int xpos, int ypos,
                      int mx, int my,
                      int w, int h,
                      int scale, int color)
{
    int startx;
    int starty;
    int x;
    int y;
    int c;

    startx = mx * w;
    starty = my * h;

    for (y = 0; y < h; y++) {
        for (x = 0; x < w; x++) {
            c = btprnt_buf_read(map,
                                startx + x,
                                starty + y);
            if (c) {
                if (scale == 1) {
                    btprnt_region_draw(reg,
                                       xpos + x, ypos + y,
                                       color);
                } else {
                    int sx, sy;
                    for (sy = 0; sy < scale; sy++) {
                        for (sx = 0; sx < scale; sx++) {
                            btprnt_region_draw(reg,
                                               xpos + x*scale + sx,
                                               ypos + y*scale + sy,
                                               color);
                        }
                    }
                }
            }
        }
    }
}

Level 2: Drawing a character

To draw the right tile, we need to be be able to match an ASCII character to the position on the map. Right now, the layout for the map matches that of many of the C64 tileset dumps found online. More details on that later.

<<funcdefs>>=
void btprnt_draw_char(btprnt_region *reg,
                      btprnt_buf *map,
                      int xpos, int ypos,
                      int w, int h,
                      char c, int scale, int color);

<<funcs>>=
void btprnt_draw_char(btprnt_region *reg,
                      btprnt_buf *map,
                      int xpos, int ypos,
                      int w, int h,
                      char c, int scale, int color)
{
    int gx, gy;
    char o;

    o = c - ' '; /* start at 0 */

    gx = o % (map->stride);
    gy = o / (map->stride);

    btprnt_draw_tile(reg, map,
                     xpos, ypos,
                     gx, gy,
                     w, h,
                     scale, color);
}

Level 3: Drawing a string

From there, a string characters can be drawn onto a region.

<<funcdefs>>=
void btprnt_draw_text(btprnt_region *reg,
                      btprnt_buf *map,
                      int xpos, int ypos,
                      int w, int h,
                      const char *str);

<<funcs>>=
void btprnt_draw_text(btprnt_region *reg,
                      btprnt_buf *map,
                      int xpos, int ypos,
                      int w, int h,
                      const char *str)
{
    int len;
    int n;
    len = strlen(str);

    for (n = 0; n < len; n++) {
        btprnt_draw_char(reg, map,
                         xpos + w*n, ypos,
                         w, h,
                         str[n], 1, 1);
    }
}

Level 4: Text wrapping

Since the dimensions of the textbox are known, some basic text wrapping can be implemented.

<<funcdefs>>=
void btprnt_draw_wraptext(btprnt_region *reg,
                          btprnt_buf *map,
                          int xpos, int ypos,
                          int w, int h,
                          const char *str);

<<funcs>>=
void btprnt_draw_wraptext(btprnt_region *reg,
                          btprnt_buf *map,
                          int xpos, int ypos,
                          int w, int h,
                          const char *str)
{
    int len;
    int n;
    int curpos;
    int line;
    int c;
    len = strlen(str);
    line = 0;
    curpos = 0;
    c = 0;

    for (n = 0; n < len; n++) {
        curpos = xpos + w*c;
        if (curpos > reg->w) {
            curpos = xpos;
            line++;
            c = 0;
        }
        btprnt_draw_char(reg, map,
                         curpos, ypos + line*h,
                         w, h,
                         str[n], 1, 1);
        c++;
    }
}

Level 5: Word Wrapping

With a bit more sophistication, some basic word wrapping can done by writing the text chunks between spaces. If a word is larger than what it left, it will know to go to the next line. If the word is larger than how many characters there are on a line, it will do the best it can to break to wrap the text up.

<<funcdefs>>=
void btprnt_draw_textbox(btprnt_region *reg,
                         btprnt_buf *map,
                         int xpos, int ypos,
                         int w, int h,
                         const char *str,
                         int scale,
                         int color);

This function works by counting characters until it reaches a space. Once it finds that space, it will write that chunk of letters up to (and including) that space. Some arithmetic will done. If it happens that the number of characters exceeds the bounds of the current line position, it will start a new line. (Space needs to be included with this count so there aren't any trailing spaces at the end of a line.) If it happens that the number of characters is greater than the length of the line, it won't matter if a newline happens, and the word will be split up as best as it can.

To be clear: a newline shouldn't happen if the number of characters in a word is longer than the width. A weird edge case I ran into involved having the first word in the textbox be long. The original code added a empty line on the first line, which looked weird.

When the text has reached the end, it has to print out the last word, if there is any. This process is pretty much indentical to what happens in the for loop, except that some of the variables updated don't matter.

For now, I literally copy-pasted this twice because I'm tired and lazy. I may come back at some point and do something more elegant when I have the time.

<<funcs>>=
void btprnt_draw_textbox(btprnt_region *reg,
                         btprnt_buf *map,
                         int xpos, int ypos,
                         int w, int h,
                         const char *str,
                         int scale,
                         int color)
{
    int len;
    int n;
    int start;
    int nchars;
    int c;
    int line;
    len = strlen(str);

    start = 0;
    nchars = 0;
    c = 0;
    line = 0;
    for (n = 0; n < len; n++) {
        nchars++;
        if (str[n] == ' ' || str[n] == '\n') {
            int wordlen;
            int off;
            int i;
            int curpos;
            char x;

            wordlen = nchars*w*scale;
            off = xpos + c*w*scale;

            if ((off + wordlen) > reg->w) {
                /* nested if is a clumsy, but it works */
                if (wordlen < reg->w) {
                    line++;
                    c = 0;
                }
            }

            for (i = 0; i < nchars; i++) {
                curpos = xpos + c*w*scale;

                x = str[start + i];

                if ((curpos + w*scale) > reg->w || x == '\n') {
                    curpos = xpos;
                    line++;
                    c = 0;
                }


                if (x != '\n') {
                    btprnt_draw_char(reg, map,
                                     curpos,
                                     ypos + line*h*scale,
                                     w, h, x, scale, color);
                    c++;
                }
            }

            start = n + 1;
            nchars = 0;
        }
    }

    if (nchars > 0) {
        /* duplicate code alert ring ring ring */
        int wordlen;
        int off;
        int i;
        int curpos;

        wordlen = nchars * w * scale;
        off = xpos + c*w*scale;

        if ((off + wordlen) > reg->w) {
            line++;
            c = 0;
        }

        for (i = 0; i < nchars; i++) {
            curpos = xpos + c*w*scale;

            if ((curpos + w*scale) > reg->w) {
                curpos = xpos;
                line++;
                c = 0;
            }

            btprnt_draw_char(reg, map,
                             curpos, ypos + line*h*scale,
                             w, h,
                             str[start + i], scale, color);
            c++;
        }

        start = n + 1;
        nchars = 0;
    }
}

Fill

The function btprnt_fill will fill a region.

<<funcdefs>>=
void btprnt_fill(btprnt_region *reg, int clr);
<<funcs>>=
void btprnt_fill(btprnt_region *reg, int clr)
{
    int x, y;
    for (y = 0; y < reg->h; y++) {
        for (x = 0; x < reg->w; x++) {
            btprnt_region_draw(reg, x, y, clr);
        }
    }
}

Lines

Horizontal Line

A horizontal line can be drawn with btprnt_draw_hline.

<<funcdefs>>=
void btprnt_draw_hline(btprnt_region *r,
                       int x, int y,
                       int sz, int clr);
<<funcs>>=
void btprnt_draw_hline(btprnt_region *r,
                       int x, int y,
                       int sz, int clr)
{
    int n;

    for (n = 0; n < sz; n++) {
        btprnt_region_draw(r, x + n, y, clr);
    }
}

Vertical Line

A horizontal line can be drawn with btprnt_draw_vline.

<<funcdefs>>=
void btprnt_draw_vline(btprnt_region *r,
                       int x, int y,
                       int sz, int clr);
<<funcs>>=
void btprnt_draw_vline(btprnt_region *r,
                       int x, int y,
                       int sz, int clr)
{
    int n;

    for (n = 0; n < sz; n++) {
        btprnt_region_draw(r, x, y + n, clr);
    }
}

DONE Regular Line

CLOSED: [2020-04-25 Sat 09:43] Bresenham circle algorithm.

<<funcdefs>>=
void btprnt_draw_line(btprnt_region *reg,
                      int x0, int y0,
                      int x1, int y1,
                      int clr);
<<funcs>>=
static void swap(int *a, int *b)
{
    int tmp;
    tmp = *a;
    *a = *b;
    *b = tmp;
}

void btprnt_draw_line(btprnt_region *reg,
                      int x0, int y0,
                      int x1, int y1,
                      int clr)
{
    int x, y;
    int dx, dy;
    int derror2;
    int error2;
    char steep = 0;

    if (abs(x0 - x1) < abs(y0 - y1)) {
        swap(&x0, &y0);
        swap(&x1, &y1);
        steep = 1;
    }

    if (x0 > x1) {
        swap(&x0, &x1);
        swap(&y0, &y1);
    }

    dx = x1 - x0;
    dy = y1 - y0;
    derror2 = abs(dy) * 2;
    error2 = 0;
    y = y0;

    for (x = x0; x < x1; x++) {
        if (steep) {
            btprnt_region_draw(reg, y, x, clr);
        } else {
            btprnt_region_draw(reg, x, y, clr);
        }
        error2 += derror2;
        if (error2 > dx) {
            y += (y1 > y0 ? 1 : -1);
            error2 -= dx * 2;
        }
    }
}

Rectangle

Stroked Rectangle

<<funcdefs>>=
void btprnt_draw_rect(btprnt_region *r,
                      int x, int y,
                      int w, int h,
                      int clr);
<<funcs>>=
void btprnt_draw_rect(btprnt_region *r,
                      int x, int y,
                      int w, int h,
                      int clr)
{
    btprnt_draw_hline(r, x, y, w, clr);
    btprnt_draw_hline(r, x, y + (h - 1), w, clr);
    btprnt_draw_vline(r, x, y, h, clr);
    btprnt_draw_vline(r, x + (w - 1), y, h, clr);
}

Filled Rectangle

<<funcdefs>>=
void btprnt_draw_rect_filled(btprnt_region *r,
                             int xpos, int ypos,
                             int w, int h,
                             int clr);
<<funcs>>=
void btprnt_draw_rect_filled(btprnt_region *r,
                             int xpos, int ypos,
                             int w, int h,
                             int clr)
{
    int x, y;

    for (y = 0; y < h; y++) {
        for (x = 0; x < w; x++) {
            btprnt_region_draw(r,
                               xpos + x,
                               ypos + y,
                               clr);
        }
    }
}

WIP Circle

Stroked Circle

Midpoint circle algorithm.

I don't know where I found the code for the first algorithm, but it was too pointy at the compass coordinates. This one is slightly more expensive, but makes for a much smoother circle: https://iq.opengenus.org/bresenhams-circle-drawing-algorithm/


That ones a bit boxy, so I tried this one: https://zcsaha.github.io/computer-graphics/midpoint-circle-drawing-algorithm-in-c.html


I'm not great either. Kind of looks like a polygon when the radius is 16. We're keeping it at that for now.

<<funcdefs>>=
void btprnt_draw_circ(btprnt_region *r,
                      int cx, int cy,
                      int rad,
                      int clr);
<<funcs>>=
static void circ_pixel(btprnt_region *r,
                       int cx, int cy,
                       int x, int y,
                       int clr)
{
    btprnt_region_draw(r, cx - x, cy + y, clr);
    btprnt_region_draw(r, cx + x, cy + y, clr);
    btprnt_region_draw(r, cx - y, cy + x, clr);
    btprnt_region_draw(r, cx + y, cy + x, clr);
    btprnt_region_draw(r, cx + x, cy - y, clr);
    btprnt_region_draw(r, cx - x, cy - y, clr);
    btprnt_region_draw(r, cx + y, cy - x, clr);
    btprnt_region_draw(r, cx - y, cy - x, clr);
}
void btprnt_draw_circ(btprnt_region *r,
                      int cx, int cy,
                      int rad,
                      int clr)
{
    int x;
    int y;
    int err;

    x = 0;
    y = rad;
    err = 1 - rad;

    circ_pixel(r, cx, cy, x, y, clr);

    while (x < y) {
        x++;

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

        circ_pixel(r, cx, cy, x, y, clr);
    }
}

Filled Circle

TODO Rounded Rectangle

Stroked Rounded Rectangle

Filled Rounded Rectangle

Triangle

A filled triangle is ideal for arrows in flowcharts!

The algorithm for this particular rasterization method is based off the one found here.

The paramters supplied are the 3 vertices of the triangle.

Note: I'm using 1-indexed variables here to better match the variables in the algorithm.

<<funcdefs>>=
void btprnt_draw_triangle(btprnt_region *r,
                          int v1x, int v1y,
                          int v2x, int v2y,
                          int v3x, int v3y,
                          int c);

Before the processing begins, vertices are sorted out in ascending order by y, making v1 the highest point.

<<sort_vertices_by_y>>=
{
    int tmpx;
    int tmpy;

    if (v1y > v2y) {
        tmpy = v1y;
        tmpx = v1x;

        v1y = v2y;
        v1x = v2x;

        v2y = tmpy;
        v2x = tmpx;
    }

    if (v1y > v3y) {
        tmpy = v1y;
        tmpx = v1x;

        v1y = v3y;
        v1x = v3x;

        v3y = tmpy;
        v3x = tmpx;
    }

    if (v2y > v3y) {
        tmpy = v2y;
        tmpx = v2x;

        v2y = v3y;
        v2x = v3x;

        v3y = tmpy;
        v3x = tmpx;
    }
}

The bresenham approach to filling involves draw two lines in parallel, and then drawing the horizontal lines between them.

This particular adaptation is from the java code, and assumes that vertices 2 + 3 sahre the same Y axis.

<<flat_triangle_fill>>=
static int signum(int x)
{
    if (x < 0) return -1;
    if (x > 0) return 1;
    else return 0;
}


static void bresenham_fill(btprnt_region *r,
                           int v1x, int v1y,
                           int v2x, int v2y,
                           int v3x, int v3y,
                           int c)
{
    int vtmp1x;
    int vtmp1y;
    int vtmp2x;
    int vtmp2y;

    int changed1;
    int changed2;

    int dx1;
    int dy1;
    int dx2;
    int dy2;

    int signx1;
    int signx2;

    int signy1;
    int signy2;

    int e1;
    int e2;

    int i;

    vtmp1x = v1x;
    vtmp1y = v1y;

    vtmp2x = v1x;
    vtmp2y = v1y;

    changed1 = 0;
    changed2 = 0;

    dx1 = abs(v2x - v1x);
    dy1 = abs(v2y - v1y);

    dx2 = abs(v3x - v1x);
    dy2 = abs(v3y - v1y);

    signx1 = signum(v2x - v1x);
    signx2 = signum(v3x - v1x);

    signy1 = signum(v2y - v1y);
    signy2 = signum(v3y - v1y);

    if (dy1 > dx1) {
        int tmp;
        tmp = dx1;
        dx1 = dy1;
        dy1 = tmp;
        changed1 = 1;
    }

    if (dy2 > dx2) {
        int tmp;
        tmp = dx2;
        dx2 = dy2;
        dy2 = tmp;
        changed2 = 1;
    }

    e1 = 2 * dy1 - dx1;
    e2 = 2 * dy2 - dx2;

    for(i = 0; i <= dx1; i++) {
        btprnt_draw_line(r, vtmp1x, vtmp1y, vtmp2x, vtmp2y, c);

        while (e1 >= 0) {
            if (changed1) vtmp1x += signx1;
            else vtmp1y += signy1;

            e1 = e1 - 2 * dx1;
        }

        if (changed1) vtmp1y += signy1;
        else vtmp1x += signx1;

        e1 = e1 + 2 * dy1;

        while (vtmp2y != vtmp1y) {
            while (e2 >= 0) {
                if (changed2) vtmp2x += signx2;
                else vtmp2y += signy2;

                e2 = e2 - 2 * dx2;
            }

            if (changed2) vtmp2y += signy2;
            else vtmp2x += signx2;

            e2 = e2 + 2 * dy2;
        }
    }

}

In the more general case, the triangle is split in half into two smaller triangles: one with a flat bottom, the other with a flat top.

<<split_the_triangle>>=
int v4x, v4y;

v4x = (v1x +
    ((float)(v2y - v1y)/(v3y - v1y)) *
    (v3x - v1x));
v4y = v2y;

bresenham_fill(r,
               v1x, v1y,
               v2x, v2y,
               v4x, v4y,
               c);

bresenham_fill(r,
               v3x, v3y,
               v2x, v2y,
               v4x, v4y,
               c);

<<funcs>>=
<<flat_triangle_fill>>

void btprnt_draw_triangle(btprnt_region *r,
                          int v1x, int v1y,
                          int v2x, int v2y,
                          int v3x, int v3y,
                          int c)
{
    <<sort_vertices_by_y>>
    if (v2y == v3y) {
        bresenham_fill(r,
                       v1x, v1y,
                       v2x, v2y,
                       v3x, v3y,
                       c);
    } if (v1y == v2y) {
        bresenham_fill(r,
                       v3x, v3y,
                       v1x, v1y,
                       v2x, v2y,
                       c);
    } else {
        <<split_the_triangle>>
    }

}

Arrow

This draws an arrowed line. One can specify if the start and end points have an arrow.

<<funcdefs>>=
void btprnt_draw_arrow(btprnt_region *r,
                       int start_x, int start_y,
                       int end_x, int end_y,
                       int arrow_start,
                       int arrow_end,
                       int draw_line,
                       int c);

Arrowheads are equalateral triangles, angled relative to the slope of the line.

<<funcs>>=
void btprnt_draw_arrow(btprnt_region *r,
                       int start_x, int start_y,
                       int end_x, int end_y,
                       int arrow_start,
                       int arrow_end,
                       int draw_line,
                       int c)
{
    int sz;
    float angle;
    float off;
    int ptx[2];
    int pty[2];
    int dx;
    int dy;
    int dir;

    if (start_x > end_x) {
        dir = -1;
    } else {
        dir = 1;
    }

    sz = 9;

    dx = end_x - start_x;
    dy = end_y - start_y;

    off = asin(dx / sqrt(dx*dx + dy*dy));

    if (off < 0) {
        dir *= -1;
    }

    angle = 30;
    /* convert to radians */
    angle *= M_PI / 180.0;

    if (draw_line) {
        btprnt_draw_line(r,
                        start_x, start_y,
                        end_x, end_y, c);
    }

    if (arrow_start) {
        ptx[0] = start_x + dir * sz * sin(angle + off);
        pty[0] = start_y + dir * sz * cos(angle + off);

        ptx[1] = start_x + dir * sz * sin(off - angle);
        pty[1] = start_y + dir * sz * cos(off - angle);

        btprnt_draw_triangle(r,
                            start_x, start_y,
                            ptx[0], pty[0],
                            ptx[1], pty[1],
                            c);
    }

    if (arrow_end) {
        ptx[0] = end_x + -dir * sz * sin(angle + off);
        pty[0] = end_y + -dir * sz * cos(angle + off);

        ptx[1] = end_x + -dir * sz * sin(off - angle);
        pty[1] = end_y + -dir * sz * cos(off - angle);

        btprnt_draw_triangle(r,
                            end_x, end_y,
                            ptx[0], pty[0],
                            ptx[1], pty[1],
                            c);

    }
}

Bezier Curve

Adapted from the bresenham bezier found at this page, as well as this stackoverflow post


<<funcdefs>>=
void btprnt_draw_bezier(btprnt_region *reg,
                        int x0, int y0,
                        int x1, int y1,
                        int x2, int y2,
                        int c);

<<funcs>>=
<<draw_bezier_seg>>
void btprnt_draw_bezier(btprnt_region *reg,
                        int x0, int y0,
                        int x1, int y1,
                        int x2, int y2,
                        int c)
{
    int x, y;
    double t, r;

    x = x0-x1;
    y = y0-y1;
    t = x0-2*x1+x2;

    if ((long)x*(x2-x1) > 0) {
        if ((long)y*(y2-y1) > 0)
            if (fabs((y0-2*y1+y2)/t*x) > abs(y)) {
                x0 = x2; x2 = x+x1; y0 = y2; y2 = y+y1;
            }
        t = (x0-x1)/t;
        r = (1-t)*((1-t)*y0+2.0*t*y1)+t*t*y2;
        t = (x0*x2-x1*x1)*t/(x0-x1);
        x = floor(t+0.5); y = floor(r+0.5);
        r = (y1-y0)*(t-x0)/(x1-x0)+y0;
        bezierseg(reg, x0, y0, x, floor(r+0.5), x, y, c);
        r = (y1-y2)*(t-x2)/(x1-x2)+y2;
        x0 = x1 = x; y0 = y; y1 = floor(r+0.5);
    }

    if ((long)(y0-y1)*(y2-y1) > 0) {
        t = y0-2*y1+y2; t = (y0-y1)/t;
        r = (1-t)*((1-t)*x0+2.0*t*x1)+t*t*x2;
        t = (y0*y2-y1*y1)*t/(y0-y1);
        x = floor(r+0.5); y = floor(t+0.5);
        r = (x1-x0)*(t-y0)/(y1-y0)+x0;
        bezierseg(reg, x0, y0, floor(r+0.5), y, x, y, c);
        r = (x1-x2)*(t-y2)/(y1-y2)+x2;
        x0 = x; x1 = floor(r+0.5); y0 = y1 = y;
    }

    bezierseg(reg, x0, y0, x1, y1, x2, y2, c);
}

<<draw_bezier_seg>>=
static void bezierseg(btprnt_region *r,
                      int x0, int y0,
                      int x1, int y1,
                      int x2, int y2,
                      int c)
{
    int sx, sy;
    long xx, yy, xy;
    double dx, dy, err, cur;
    int rc;

    sx = x2-x1;
    sy = y2-y1;
    xx = x0-x1;
    yy = y0-y1;

    cur = xx*sy-yy*sx;

    rc = xx*sx <= 0 && yy*sy <= 0;

    if (!rc) return;

    if (sx*(long)sx+sy*(long)sy > xx*xx+yy*yy) {
        x2 = x0;
        x0 = sx+x1;
        y2 = y0;
        y0 = sy+y1;
        cur = -cur;
    }

    if (cur != 0) {
        xx += sx;
        xx *= sx = x0 < x2 ? 1 : -1;

        yy += sy;
        yy *= sy = y0 < y2 ? 1 : -1;

        xy = 2*xx*yy;
        xx *= xx;
        yy *= yy;

        if (cur*sx*sy < 0) {
            xx = -xx;
            yy = -yy;
            xy = -xy;
            cur = -cur;
        }

        dx = 4.0*sy*cur*(x1-x0)+xx-xy;
        dy = 4.0*sx*cur*(y0-y1)+yy-xy;

        xx += xx;
        yy += yy;
        err = dx+dy+xy;

        do {
            btprnt_region_draw(r, x0, y0, c);

            if (x0 == x2 && y0 == y2) return;

            y1 = 2*err < dx;

            if (2*err > dy) {
                x0 += sx;
                dx -= xy;
                err += dy += yy;
            }

            if (y1) {
                y0 += sy;
                dy -= xy;
                err += dx += xx;
            }
        } while (dy < dx );
    }

    btprnt_draw_line(r, x0, y0, x2, y2, c);
}

Bezier Arrow

Like an arrow, but with a bezier curved line.

<<funcdefs>>=
void btprnt_draw_bezier_arrow(btprnt_region *r,
                              int start_x, int start_y,
                              int end_x, int end_y,
                              int ctrl_x, int ctrl_y,
                              int arrow_start,
                              int arrow_end,
                              int c);

Internally, this is done by drawing two arrowheads pointing away from a control point, then drawing a bezier curve connecting those two points.

<<funcs>>=
void btprnt_draw_bezier_arrow(btprnt_region *r,
                              int start_x, int start_y,
                              int end_x, int end_y,
                              int ctrl_x, int ctrl_y,
                              int arrow_start,
                              int arrow_end,
                              int c)
{
    btprnt_draw_arrow(r,
                      start_x, start_y,
                      ctrl_x, ctrl_y,
                      1, 0, 0, c);

    btprnt_draw_arrow(r,
                      end_x, end_y,
                      ctrl_x, ctrl_y,
                      1, 0, 0, c);

    btprnt_draw_bezier(r,
                       start_x, start_y,
                       ctrl_x, ctrl_y,
                       end_x, end_y,
                       c);
}