Skip to content

Commit

Permalink
extmod/modframebuf: Add ellipse drawing method.
Browse files Browse the repository at this point in the history
  • Loading branch information
peterhinch authored and dpgeorge committed Aug 19, 2022
1 parent 127b340 commit 42ec970
Show file tree
Hide file tree
Showing 5 changed files with 885 additions and 6 deletions.
18 changes: 16 additions & 2 deletions docs/library/framebuf.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ class FrameBuffer
-----------------

The FrameBuffer class provides a pixel buffer which can be drawn upon with
pixels, lines, rectangles, text and even other FrameBuffer's. It is useful
when generating output for displays.
pixels, lines, rectangles, ellipses, text and even other FrameBuffers. It is
useful when generating output for displays.

For example::

Expand Down Expand Up @@ -84,6 +84,20 @@ The following methods draw shapes onto the FrameBuffer.
The optional *f* parameter can be set to ``True`` to fill the rectangle.
Otherwise just a one pixel outline is drawn.

.. method:: FrameBuffer.ellipse(x, y, xr, yr, c[, f, m])

Draw an ellipse at the given location. Radii *xr* and *yr* define the
geometry; equal values cause a circle to be drawn. The *c* parameter
defines the color.

The optional *f* parameter can be set to ``True`` to fill the ellipse.
Otherwise just a one pixel outline is drawn.

The optional *m* parameter enables drawing to be restricted to certain
quadrants of the ellipse. The LS four bits determine which quadrants are
to be drawn, with bit 0 specifying Q1, b1 Q2, b2 Q3 and b3 Q4. Quadrants
are numbered counterclockwise with Q1 being top right.

Drawing text
------------

Expand Down
9 changes: 5 additions & 4 deletions examples/natmod/framebuf/framebuf.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ mp_obj_type_t mp_type_framebuf;

#include "extmod/modframebuf.c"

mp_map_elem_t framebuf_locals_dict_table[10];
mp_map_elem_t framebuf_locals_dict_table[11];
STATIC MP_DEFINE_CONST_DICT(framebuf_locals_dict, framebuf_locals_dict_table);

mp_obj_t mpy_init(mp_obj_fun_bc_t *self, size_t n_args, size_t n_kw, mp_obj_t *args) {
Expand All @@ -29,9 +29,10 @@ mp_obj_t mpy_init(mp_obj_fun_bc_t *self, size_t n_args, size_t n_kw, mp_obj_t *a
framebuf_locals_dict_table[4] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_vline), MP_OBJ_FROM_PTR(&framebuf_vline_obj) };
framebuf_locals_dict_table[5] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_rect), MP_OBJ_FROM_PTR(&framebuf_rect_obj) };
framebuf_locals_dict_table[6] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_line), MP_OBJ_FROM_PTR(&framebuf_line_obj) };
framebuf_locals_dict_table[7] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_blit), MP_OBJ_FROM_PTR(&framebuf_blit_obj) };
framebuf_locals_dict_table[8] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_scroll), MP_OBJ_FROM_PTR(&framebuf_scroll_obj) };
framebuf_locals_dict_table[9] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_text), MP_OBJ_FROM_PTR(&framebuf_text_obj) };
framebuf_locals_dict_table[7] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_ellipse), MP_OBJ_FROM_PTR(&framebuf_ellipse_obj) };
framebuf_locals_dict_table[8] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_blit), MP_OBJ_FROM_PTR(&framebuf_blit_obj) };
framebuf_locals_dict_table[9] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_scroll), MP_OBJ_FROM_PTR(&framebuf_scroll_obj) };
framebuf_locals_dict_table[10] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_text), MP_OBJ_FROM_PTR(&framebuf_text_obj) };
mp_type_framebuf.locals_dict = (void*)&framebuf_locals_dict;

mp_store_global(MP_QSTR_FrameBuffer, MP_OBJ_FROM_PTR(&mp_type_framebuf));
Expand Down
95 changes: 95 additions & 0 deletions extmod/modframebuf.c
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,100 @@ STATIC mp_obj_t framebuf_line(size_t n_args, const mp_obj_t *args_in) {
}
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(framebuf_line_obj, 6, 6, framebuf_line);

STATIC void ellipse_pixel(const mp_obj_framebuf_t *fb, mp_int_t x, mp_int_t y, mp_int_t col, mp_int_t mask) {
if (mask && 0 <= x && x < fb->width && 0 <= y && y < fb->height) {
setpixel(fb, x, y, col);
}
}

// Q2 Q1
// Q3 Q4
#define ELLIPSE_MASK_FILL (0x10)
#define ELLIPSE_MASK_ALL (0x0f)
#define ELLIPSE_MASK_Q1 (0x01)
#define ELLIPSE_MASK_Q2 (0x02)
#define ELLIPSE_MASK_Q3 (0x04)
#define ELLIPSE_MASK_Q4 (0x08)

STATIC void draw_ellipse_points(const mp_obj_framebuf_t *fb, mp_int_t cx, mp_int_t cy, mp_int_t x, mp_int_t y, mp_int_t col, mp_int_t mask) {
if (mask & ELLIPSE_MASK_FILL) {
if (mask & ELLIPSE_MASK_Q1) {
fill_rect(fb, cx, cy - y, x + 1, 1, col);
}
if (mask & ELLIPSE_MASK_Q2) {
fill_rect(fb, cx - x, cy - y, x + 1, 1, col);
}
if (mask & ELLIPSE_MASK_Q3) {
fill_rect(fb, cx - x, cy + y, x + 1, 1, col);
}
if (mask & ELLIPSE_MASK_Q4) {
fill_rect(fb, cx, cy + y, x + 1, 1, col);
}
} else {
ellipse_pixel(fb, cx + x, cy - y, col, mask & ELLIPSE_MASK_Q1);
ellipse_pixel(fb, cx - x, cy - y, col, mask & ELLIPSE_MASK_Q2);
ellipse_pixel(fb, cx - x, cy + y, col, mask & ELLIPSE_MASK_Q3);
ellipse_pixel(fb, cx + x, cy + y, col, mask & ELLIPSE_MASK_Q4);
}
}

STATIC mp_obj_t framebuf_ellipse(size_t n_args, const mp_obj_t *args_in) {
mp_obj_framebuf_t *self = MP_OBJ_TO_PTR(args_in[0]);
mp_int_t args[5];
framebuf_args(args_in, args, 5); // cx, cy, xradius, yradius, col
mp_int_t mask = (n_args > 6 && mp_obj_is_true(args_in[6])) ? ELLIPSE_MASK_FILL : 0;
if (n_args > 7) {
mask |= mp_obj_get_int(args_in[7]) & ELLIPSE_MASK_ALL;
} else {
mask |= ELLIPSE_MASK_ALL;
}
mp_int_t two_asquare = 2 * args[2] * args[2];
mp_int_t two_bsquare = 2 * args[3] * args[3];
mp_int_t x = args[2];
mp_int_t y = 0;
mp_int_t xchange = args[3] * args[3] * (1 - 2 * args[2]);
mp_int_t ychange = args[2] * args[2];
mp_int_t ellipse_error = 0;
mp_int_t stoppingx = two_bsquare * args[2];
mp_int_t stoppingy = 0;
while (stoppingx >= stoppingy) { // 1st set of points, y' > -1
draw_ellipse_points(self, args[0], args[1], x, y, args[4], mask);
y += 1;
stoppingy += two_asquare;
ellipse_error += ychange;
ychange += two_asquare;
if ((2 * ellipse_error + xchange) > 0) {
x -= 1;
stoppingx -= two_bsquare;
ellipse_error += xchange;
xchange += two_bsquare;
}
}
// 1st point set is done start the 2nd set of points
x = 0;
y = args[3];
xchange = args[3] * args[3];
ychange = args[2] * args[2] * (1 - 2 * args[3]);
ellipse_error = 0;
stoppingx = 0;
stoppingy = two_asquare * args[3];
while (stoppingx <= stoppingy) { // 2nd set of points, y' < -1
draw_ellipse_points(self, args[0], args[1], x, y, args[4], mask);
x += 1;
stoppingx += two_bsquare;
ellipse_error += xchange;
xchange += two_bsquare;
if ((2 * ellipse_error + ychange) > 0) {
y -= 1;
stoppingy -= two_asquare;
ellipse_error += ychange;
ychange += two_asquare;
}
}
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(framebuf_ellipse_obj, 6, 8, framebuf_ellipse);

STATIC mp_obj_t framebuf_blit(size_t n_args, const mp_obj_t *args_in) {
mp_obj_framebuf_t *self = MP_OBJ_TO_PTR(args_in[0]);
mp_obj_t source_in = mp_obj_cast_to_native_base(args_in[1], MP_OBJ_FROM_PTR(&mp_type_framebuf));
Expand Down Expand Up @@ -603,6 +697,7 @@ STATIC const mp_rom_map_elem_t framebuf_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_vline), MP_ROM_PTR(&framebuf_vline_obj) },
{ MP_ROM_QSTR(MP_QSTR_rect), MP_ROM_PTR(&framebuf_rect_obj) },
{ MP_ROM_QSTR(MP_QSTR_line), MP_ROM_PTR(&framebuf_line_obj) },
{ MP_ROM_QSTR(MP_QSTR_ellipse), MP_ROM_PTR(&framebuf_ellipse_obj) },
{ MP_ROM_QSTR(MP_QSTR_blit), MP_ROM_PTR(&framebuf_blit_obj) },
{ MP_ROM_QSTR(MP_QSTR_scroll), MP_ROM_PTR(&framebuf_scroll_obj) },
{ MP_ROM_QSTR(MP_QSTR_text), MP_ROM_PTR(&framebuf_text_obj) },
Expand Down
65 changes: 65 additions & 0 deletions tests/extmod/framebuf_ellipse.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
try:
import framebuf
except ImportError:
print("SKIP")
raise SystemExit


def printbuf():
print("--8<--")
for y in range(h):
for x in range(w):
print("%02x" % buf[(x + y * w)], end="")
print()
print("-->8--")


w = 30
h = 30
buf = bytearray(w * h)
fbuf = framebuf.FrameBuffer(buf, w, h, framebuf.GS8)

# Outline
fbuf.fill(0)
fbuf.ellipse(15, 15, 12, 6, 0xFF, False)
printbuf()

# Fill
fbuf.fill(0)
fbuf.ellipse(15, 15, 6, 12, 0xAA, True)
printbuf()

# Outline and fill some different quadrant combos.
for m in (0, 0b0001, 0b0010, 0b0100, 0b1000, 0b1010):
fbuf.fill(0)
fbuf.ellipse(15, 15, 6, 12, 0xAA, False, m)
printbuf()
fbuf.fill(0)
fbuf.ellipse(15, 15, 6, 12, 0xAA, True, m)
printbuf()

# Draw ellipses that will go out of bounds at each of the edges.
for x, y in (
(
4,
4,
),
(
26,
4,
),
(
26,
26,
),
(
4,
26,
),
):
fbuf.fill(0)
fbuf.ellipse(x, y, 6, 12, 0xAA, False)
printbuf()
fbuf.fill(0)
fbuf.ellipse(x, y, 6, 12, 0xAA, True)
printbuf()
Loading

0 comments on commit 42ec970

Please sign in to comment.