GfxLCD and ILI9325 C driver – part 2

This time we will work with drawing functions. Displaying pixel is easy but next, we have a line, circle and arc. I wonder how hard or easy moving functions from Python to C gonna be.

In Python implementation of GfxLCD, we use @property to access width, height or set colours. We need to do same with C implementation. I see two ways, one we will use Python class that would translate property to function call in C or maybe we can use some ability of Python-C wrapper.

GfxLCD and ILI9325 C driver – part 1

Pixel

The first function that does some drawing wasย fill_rect but all others are waiting for implementation.
First, draw_pixel, as it is the simplest one and other use it. Files ili.c/.h:

void draw_pixel(ILIObject *self, uint16_t pos_x1, uint16_t pos_y1);

void draw_pixel(ILIObject *self, uint16_t pos_x1, uint16_t pos_y1) {
    set_area(self, pos_x1, pos_y1, pos_x1, pos_y1);
    data(self, self->color);
}

Docblock, declaration and proxy in _cili9325.c:

static char draw_pixel_docstring[] =  "draw_pixel";
..
static PyObject *ili_draw_pixel(ILIObject *self, PyObject *args);
..
    {"draw_pixel", (PyCFunction)ili_draw_pixel, METH_VARARGS, draw_pixel_docstring},
..
static PyObject *ili_draw_pixel(ILIObject *self, PyObject *args) {
    int pos_x1, pos_y1;
    if (!PyArg_ParseTuple(args, "II", &pos_x1, &pos_y1)) {
        return NULL;
    }
    draw_pixel(self, pos_x1, pos_y1);

    return Py_None;
}

Usage:

lcd.set_color(255,0,0)
lcd.draw_pixel(120,160)

Drawing pixel is a basic functionality and we have it. Next, drawing a line.

Line

We start with some definitions and call to function from ili.c:

static char draw_line_docstring[] =
    "draw_line";
..
static PyObject *ili_draw_line(ILIObject *self, PyObject *args);
..
    {"draw_line", (PyCFunction)ili_draw_line, METH_VARARGS, draw_line_docstring},
..
static PyObject *ili_draw_line(ILIObject *self, PyObject *args) {
    int pos_x1, pos_x2, pos_y1, pos_y2;
    if (!PyArg_ParseTuple(args, "IIII", &pos_x1, &pos_y1, &pos_x2, &pos_y2)) {
        return NULL;
    }
    draw_line(self, pos_x1, pos_y1, pos_x2, pos_y2);

    return Py_None;
}

Our drawing algorithm takes diagonal lines and divides them into horizontal or vertical parts. So first we need simple drawing:

void draw_vertical_line(ILIObject *self, uint16_t pos_x1, uint16_t pos_y1, uint16_t length) {
    set_area(self, pos_x1, pos_y1, pos_x1, pos_y1 + length);
    int i;
    for(i=0; i<length; i++){ data(self, self->color);
    }
}

void draw_horizontal_line(ILIObject *self, uint16_t pos_x1, uint16_t pos_y1, uint16_t length) {
    set_area(self, pos_x1, pos_y1, pos_x1 + length, pos_y1);
    int i;
    for(i=0; i<length; i++){ data(self, self->color);
    }
}

Time for the hard part, in Python we divide line and return an array with length but in C it is not so easy ๐Ÿ™‚ Time for pointers!

I totally forgot all about C so I will take small steps. First, line drawing without length correction:

void calculate_line_steps(int *steps, uint16_t length, uint16_t step, uint16_t required_length) {
    int i;
    for (i=0; i<step; i++) {
        steps[i] = length;
    }
}

This is a stub so do not worry ๐Ÿ™‚

void draw_line(ILIObject *self, uint16_t pos_x1, uint16_t pos_y1, uint16_t pos_x2, uint16_t pos_y2) {
    uint16_t width = abs(pos_x2 - pos_x1);
    uint16_t height = abs(pos_y2 - pos_y1);
    int horizontal;
    int offset_x=0, offset_y=0;

    int step = 0, required_length = 0, length = 0;

We start with initialization and basic calculation.

    if (pos_x1 == pos_x2) {
        draw_vertical_line(self, pos_x1, pos_y1, height+1);

        return;
    }
    if (pos_y1 == pos_y2) {
        draw_horizontal_line(self, pos_x1, pos_y1, width+1);

        return;
    }

A horizontal or vertical line can be drawn at once.

    if (width > height) {
        horizontal = 1;
        width += 1;
        if (pos_x2 < pos_x1) { swap(&pos_x2, &pos_x1); swap(&pos_y2, &pos_y1); } if (pos_y2 > pos_y1) {
            offset_y = 1;
        } else {
            offset_y = -1;
        }
        if (pos_x2 > pos_x1) {
            offset_x = 1;
        } else {
            offset_x = -1;
        }
        length = round(width / (height + 1));
        step = height + 1;
        required_length = width;
    } else {
        horizontal = 0;
        height += 1;
        if (pos_y2 < pos_y1) { swap(&pos_x2, &pos_x1); swap(&pos_y2, &pos_y1); } if (pos_y2 > pos_y1) {
            offset_y = 1;
        } else {
            offset_y = -1;
        }
        if (pos_x2 > pos_x1) {
            offset_x = 1;
        } else {
            offset_x = -1;
        }
        length = round(height / (width + 1));
        step = width + 1;
        required_length = height;
    }

Uff some more math, we are doing same as in Python, calculate subparts and correct coords if needed.

    int *steps = (int*)calloc(step, sizeof(int*));
    calculate_line_steps(steps, length, step, required_length);

    int delta_x=0, delta_y=0, i;

And finally drawing using horizontal or vertical lines:

int delta_x=0, delta_y=0, i;
    printf("length = %d \n ", step);
    printf("[0] = %d lub %d \n ", steps[0], steps[1]);
    printf("horizontal = %d \n", horizontal);
    for (i=0; i<step;i++) {
        printf("i = %d \n", i);
        if (horizontal == 1) {
            draw_horizontal_line(
                self,
                pos_x1 + delta_x,
                pos_y1 + i * offset_y,
                steps[i]
            );
            delta_x += *(steps + i) * offset_x;
        } else {
            draw_vertical_line(
                self,
                pos_x1 + (i * offset_x),
                pos_y1 + delta_y,
                steps[i]
            );
            printf("x = %d, y = %d, l= %d \n", pos_x1 + (i * offset_x), pos_y1 + delta_y, steps[i]);
            delta_y += *(steps + i) * offset_y;
        }
    }
    free(steps);

Getting friendly with pointers took me some time but it was worth ๐Ÿ™‚ As it is not possible to return an array from the function we pass our array to function calculate_line_steps and it works on it.

This function got some serious work to do but we will start with simple dummy:

void calculate_line_steps(int *steps, uint16_t length, uint16_t step, uint16_t required_length) {
    int i;
    for (i=0; i<step; i++) {
        steps[i] = length;
    }

}

We do not append missing length just draw anything to get it work and then refactor to what we want.

Compile and it works…almost…Line is drawn but with some distortion :/

With each run, distortion is in a different place.
Why?
Probably another delay problem but where?
Damm, Ok leave it for now. Let’s finish this function. Now we need to add nice propagation of pixels that are still to add.

int calculate_line_appendix(int appendix) {
    if (appendix == 0) {
        return -1;
    }
    if (appendix < 0) {
        return appendix * -1;
    }
    return (appendix + 1) * -1;
}

void calculate_line_steps(int *steps, uint16_t length, uint16_t step, uint16_t required_length) {
    int i;
    for (i=0; i<step; i++) {
        steps[i] = length;
    }   
    if (step * length < required_length) {
        int rest = required_length - step * length;
        int offset = round(step / 2);
        int steps_even = step % 2;
        int rest_even = rest % 2;
        int appendix = 0;
        for (i=0; i<rest; i++) { steps[offset + appendix] += 1; if (steps_even == 0) { appendix = calculate_line_appendix(appendix); } else if (i > 0 && rest_even == 0) {
                appendix = calculate_line_appendix(appendix);
            } else if (rest_even > 0) {
                appendix = calculate_line_appendix(appendix);
            }

        }
    }
}

And nice and beautiful epic fail.

Lines are drawn totally without any common sense ๐Ÿ˜€ Funny that it is something that I saw just now. It is mostly ok when I used it in my projects. The whole algorithm is wrong and cannot be easily fixed. We need to rewrite it, here and in Python. But not now.
Our goal is to have all functionality from GfxLCD Python in C and use SPI connection.

But a small change in priorities. We will add a circle, arc drawing and SPI. Then, we will write a new line algorithm.

Circle and arc

First, we need to add function’s definitions to ili.h:

void draw_circle(ILIObject *self, uint16_t pos_x, uint16_t pos_y, uint16_t radius);
void draw_arc(ILIObject *self, uint16_t pos_x, uint16_t pos_y, uint16_t radius, uint16_t start, uint16_t end);

Now same as always, docblock, definitions and proxy (in _cili9325.c):

static char draw_circle_docstring[] =
    "draw_circle";
static char draw_arc_docstring[] =
    "draw_arc";

..

static PyObject *ili_draw_circle(ILIObject *self, PyObject *args);
static PyObject *ili_draw_arc(ILIObject *self, PyObject *args);

..

    {"draw_circle", (PyCFunction)ili_draw_circle, METH_VARARGS, draw_circle_docstring},
    {"draw_arc", (PyCFunction)ili_draw_arc, METH_VARARGS, draw_arc_docstring},

..

static PyObject *ili_draw_circle(ILIObject *self, PyObject *args) {
    int pos_x, pos_x, radius;
    if (!PyArg_ParseTuple(args, "III", &pos_x, &pos_y, &radius)) {
        return NULL;
    }
    //draw_circle(self, pos_x1, pos_y1, radius);

    return Py_None;
}

static PyObject *ili_draw_arc(ILIObject *self, PyObject *args) {
    int pos_x, pos_x, radius, start, end;
    if (!PyArg_ParseTuple(args, "IIIII", &pos_x, &pos_y, &radius, &start, &end)) {
        return NULL;
    }
    //draw_arc(self, pos_x1, pos_y1, radius, start, end);

    return Py_None;
}

Uff, preparation done. I’m getting used to it. Let’s implement circle drawing (in ili.c)

void draw_circle(ILIObject *self, uint16_t pos_x, uint16_t pos_y, uint16_t radius) {
    int err=0, offset_x = radius, offset_y=0;
    while (offset_x >= offset_y) {
        draw_pixel(self, pos_x + offset_x, pos_y + offset_y);
        draw_pixel(self, pos_x + offset_y, pos_y + offset_x);
        draw_pixel(self, pos_x - offset_y, pos_y + offset_x);
        draw_pixel(self, pos_x - offset_x, pos_y + offset_y);
        draw_pixel(self, pos_x - offset_x, pos_y - offset_y);
        draw_pixel(self, pos_x - offset_y, pos_y - offset_x);
        draw_pixel(self, pos_x + offset_y, pos_y - offset_x);
        draw_pixel(self, pos_x + offset_x, pos_y - offset_y);
        if (err <= 0) {
            offset_y += 1;
            err += 2*offset_y + 1;
        } else {
            offset_x -= 1;
            err -= 2*offset_x + 1;
        }
    }
}

We can use it from Python like this:

lcd.draw_circle(120, 175, 100);

To my surprise it worked ๐Ÿ™‚ What about the arc:

void draw_arc(ILIObject *self, uint16_t pos_x, uint16_t pos_y, uint16_t radius, uint16_t start, uint16_t end) {
    int err=0, offset_x = radius, offset_y=0;
    double start_r, end_r;
    start_r = start * M_PI / 180;
    end_r = end * M_PI / 180;
    while (offset_x >= offset_y) {
        if (start_r <= atan2(offset_y, offset_x) && atan2(offset_y, offset_x) <= end_r) {draw_pixel(self, pos_x + offset_x, pos_y + offset_y);}
        if (start_r <= atan2(offset_x, offset_y) && atan2(offset_x, offset_y) <= end_r) {draw_pixel(self, pos_x + offset_y, pos_y + offset_x);}
        if (start_r <= atan2(offset_x, -offset_y) && atan2(offset_x, -offset_y) <= end_r) {draw_pixel(self, pos_x - offset_y, pos_y + offset_x);}
        if (start_r <= atan2(offset_y, -offset_x) && atan2(offset_y, -offset_x) <= end_r) {draw_pixel(self, pos_x - offset_x, pos_y + offset_y);}
        if (start_r <= atan2(-offset_y, -offset_x) + 2*M_PI && atan2(-offset_y, -offset_x) + 2*M_PI <= end_r) {draw_pixel(self, pos_x - offset_x, pos_y - offset_y);}
        if (start_r <= atan2(-offset_x, -offset_y) + 2*M_PI && atan2(-offset_x, -offset_y) + 2*M_PI <= end_r) {draw_pixel(self, pos_x - offset_y, pos_y - offset_x);}
        if (start_r <= atan2(-offset_x, offset_y) + 2*M_PI && atan2(-offset_x, offset_y) + 2*M_PI <= end_r) {draw_pixel(self, pos_x + offset_y, pos_y - offset_x);}
        if (start_r <= atan2(-offset_y, offset_x) + 2*M_PI && atan2(-offset_y, offset_x) + 2*M_PI <= end_r) {draw_pixel(self, pos_x + offset_x, pos_y - offset_y);}
        if (err <= 0) {
            offset_y += 1;
            err += 2*offset_y + 1;
        } else {
            offset_x -= 1;
            err -= 2*offset_x + 1;
        }
    }
}

It works too, nice.
And by accident I found out that we do not have a draw_rect function, so quick implementation:

void draw_rect(ILIObject *self, uint16_t pos_x1, uint16_t pos_y1, uint16_t pos_x2, uint16_t pos_y2) {
        draw_line(self, pos_x1, pos_y1, pos_x2, pos_y1);
        draw_line(self, pos_x1, pos_y2, pos_x2, pos_y2);
        draw_line(self, pos_x1, pos_y1, pos_x1, pos_y2);
        draw_line(self, pos_x2, pos_y1, pos_x2, pos_y2);
}

Getters & setters

Just before the end let’s add setters and/or getters for: width, height, colour, background colour, auto_flush. In Python’s GfxLCD we use:

lcd.color = (255,0,0)

and not

lcd.set_color(255,0,0)

Fortunately, we can write a magic get/set in C. Doing it is similar to normal function.
First lots of definitions:

static PyObject *magic_get_width(ILIObject *self, void *closure);
static PyObject *magic_get_height(ILIObject *self, void *closure);
static PyObject *magic_get_color(ILIObject *self, void *closure);
static int *magic_set_color(ILIObject *self, PyObject *value, void *closure);
static PyObject *magic_get_background_color(ILIObject *self, void *closure);
static int *magic_set_background_color(ILIObject *self, PyObject *value, void *closure);
static PyObject *magic_get_auto_flush(ILIObject *self, void *closure);
static int *magic_set_auto_flush(ILIObject *self, PyObject *value, void *closure);

static PyGetSetDef ili_getset[] = {
    {"width", (getter)magic_get_width, NULL, "width", NULL},
    {"height", (getter)magic_get_height, NULL, "height", NULL},
    {"color", (getter)magic_get_color, (setter)magic_set_color, "color", NULL},
    {"background_color", (getter)magic_get_background_color, (setter)magic_set_background_color, "background", NULL},
    {"auto_flush", (getter)magic_get_auto_flush, (setter)magic_set_auto_flush, "auto_flush", NULL},
    {NULL}
};

Now, we need to pass the ili_getset array to type definition:

static PyTypeObject ILIType = {
..
    module_methods,            /* tp_methods */
    0,                         /* tp_members */
    ili_getset,                /* tp_getset */
..

And finally, add functions that we declared:

static PyObject *magic_get_width(ILIObject *self, void *closure) {
    return Py_BuildValue("i", self->width);
}

static PyObject *magic_get_height(ILIObject *self, void *closure) {
    return Py_BuildValue("i", self->height);
}

static PyObject *magic_get_color(ILIObject *self, void *closure) {
    return Py_BuildValue("i", self->color);
}

static int *magic_set_color(ILIObject *self, PyObject *args, void *closure) {
    int r,g,b;
    r = PyLong_AsLong(PyTuple_GetItem(args, 0));
    g = PyLong_AsLong(PyTuple_GetItem(args, 1));
    b = PyLong_AsLong(PyTuple_GetItem(args, 2));

    set_color(self, r, g, b);

    return 0;
}

static PyObject *magic_get_background_color(ILIObject *self, void *closure) {
    return Py_BuildValue("i", self->background_color);
}

static int *magic_set_background_color(ILIObject *self, PyObject *args, void *closure) {

    int r,g,b;
    r = PyLong_AsLong(PyTuple_GetItem(args, 0));
    g = PyLong_AsLong(PyTuple_GetItem(args, 1));
    b = PyLong_AsLong(PyTuple_GetItem(args, 2));

    set_backgroundcolor(self, r, g, b);

    return 0;
}

static PyObject *magic_get_auto_flush(ILIObject *self, void *closure) {
    return Py_BuildValue("B", 1);
}

static int *magic_set_auto_flush(ILIObject *self, PyObject *value, void *closure) {
    return 0;
}

It wasn’t complicated and the effect is nice:

lcd = cili9325.ili(320, 240, 18, 27, 17, 25, 22, 23, 24, 5, 12, 16, 20, 21)

print(lcd.width, lcd.height)
lcd.init_display()
lcd.background_color = (255,255,255)
lcd.fill_rect(0,0,100,200)

We have magic methods without any Python code! First I thought that we need a Python class to wrap C class but seems we can do without it! Just pure C code!

Ok. this post is long enough,ย let’s stop here.

Summary

This time we found out that drawing the vertical line doesn’t work, we need to get a better algorithm.
But drawing a pixel, a circle, an arc and a rectangle is OK ๐Ÿ™‚
We also implemented magic get and set in pure C without Python class.
What now?
We need to check SPI communication, that means ILI9486 in action ๐Ÿ™‚ Then we can work on the line drawing.

And our docblocks are only stubs, something to upgrade in free time ๐Ÿ˜€

So much work and so little time…

 

Advertisements

2 comments

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s