A simple yet powerful library for controlling graphical displays. Multiple display controllers are supported using inheritance.

Dependents:   mbed_rifletool Hexi_Bubble_Game Hexi_Catch-the-dot_Game Hexi_Acceleromagnetic_Synth

NOTE: This library is in beta right now. As far as I know, everything here works, but there are many features that are lacking so far. Most notably containers, button handling, and display drivers other than the SSD1306.

Abstracts/Canvas.cpp

Committer:
neilt6
Date:
2013-08-30
Revision:
0:b876cf091464
Child:
1:f7003ec66a51

File content as of revision 0:b876cf091464:

/* NeatGUI Library
 * Copyright (c) 2013 Neil Thiessen
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "Canvas.h"

Canvas::Canvas(int w, int h)
{
    m_Width = w;
    m_Height = h;
}

void Canvas::clear(unsigned int c)
{
    fillRect(0, 0, m_Width, m_Height, c);
}

void Canvas::drawLine(int x0, int y0, int x1, int y1, unsigned int c)
{
    int steep = abs(y1 - y0) > abs(x1 - x0);
    if (steep) {
        int temp = x0;
        x0 = y0;
        y0 = temp;

        temp = x1;
        x1 = y1;
        y1 = temp;
    }

    if (x0 > x1) {
        int temp = x0;
        x0 = x1;
        x1 = temp;

        temp = y0;
        y0 = y1;
        y1 = temp;
    }

    int dx, dy;
    dx = x1 - x0;
    dy = abs(y1 - y0);

    int err = dx / 2;
    int ystep;

    if (y0 < y1) {
        ystep = 1;
    } else {
        ystep = -1;
    }

    for (; x0 <= x1; x0++) {
        if (steep) {
            drawPixel(y0, x0, c);
        } else {
            drawPixel(x0, y0, c);
        }
        err -= dy;
        if (err < 0) {
            y0 += ystep;
            err += dx;
        }
    }
}

void Canvas::drawHLine(int x, int y, int w, unsigned int c)
{
    drawLine(x, y, x + w - 1, y, c);
}

void Canvas::drawVLine(int x, int y, int h, unsigned int c)
{
    drawLine(x, y, x, y + h - 1, c);
}

void Canvas::drawRect(int x, int y, int w, int h, unsigned int c)
{
    drawHLine(x, y, w, c);
    drawHLine(x, y + h - 1, w, c);
    drawVLine(x, y, h, c);
    drawVLine(x + w - 1, y, h, c);
}

void Canvas::fillRect(int x, int y, int w, int h, unsigned int c)
{
    for (int i = x; i < x + w; i++) {
        drawVLine(i, y, h, c);
    }
}

void Canvas::drawTriangle(int x0, int y0, int x1, int y1, int x2, int y2, unsigned int c)
{
    drawLine(x0, y0, x1, y1, c);
    drawLine(x1, y1, x2, y2, c);
    drawLine(x2, y2, x0, y0, c);
}

void Canvas::fillTriangle(int x0, int y0, int x1, int y1, int x2, int y2, unsigned int c)
{
    int a, b, y, last;

    //Sort coordinates by Y order (y2 >= y1 >= y0)
    if (y0 > y1) {
        int temp = y0;
        y0 = y1;
        y1 = temp;

        temp = x0;
        x0 = x1;
        x1 = temp;
    }
    if (y1 > y2) {
        int temp = y1;
        x1 = y2;
        y2 = temp;

        temp = x1;
        x1 = x2;
        x2 = temp;
    }
    if (y0 > y1) {
        int temp = y0;
        y0 = y1;
        y1 = temp;

        temp = x0;
        x0 = x1;
        x1 = temp;
    }

    //Handle awkward all-on-same-line case as its own thing
    if(y0 == y2) {
        a = b = x0;
        if(x1 < a)
            a = x1;
        else if(x1 > b)
            b = x1;
        if(x2 < a)
            a = x2;
        else if(x2 > b)
            b = x2;

        drawHLine(a, y0, b - a + 1, c);

        return;
    }

    int dx01 = x1 - x0;
    int dy01 = y1 - y0;
    int dx02 = x2 - x0;
    int dy02 = y2 - y0;
    int dx12 = x2 - x1;
    int dy12 = y2 - y1;
    int sa = 0;
    int sb = 0;

    //For upper part of triangle, find scanline crossings for segments
    //0-1 and 0-2.  If y1=y2 (flat-bottomed triangle), the scanline y1
    //is included here (and second loop will be skipped, avoiding a /0
    //error there), otherwise scanline y1 is skipped here and handled
    //in the second loop...which also avoids a /0 error here if y0=y1
    //(flat-topped triangle).
    if(y1 == y2)
        last = y1;      //Include y1 scanline
    else
        last = y1 - 1;  //Skip it

    for(y = y0; y <= last; y++) {
        a = x0 + sa / dy01;
        b = x0 + sb / dy02;
        sa += dx01;
        sb += dx02;

        if(a > b) {
            int temp = a;
            a = b;
            b = temp;
        }
        drawHLine(a, y, b - a + 1, c);
    }

    //For lower part of triangle, find scanline crossings for segments
    //0-2 and 1-2.  This loop is skipped if y1=y2.
    sa = dx12 * (y - y1);
    sb = dx02 * (y - y0);
    for(; y <= y2; y++) {
        a = x1 + sa / dy12;
        b = x0 + sb / dy02;
        sa += dx12;
        sb += dx02;

        if(a > b) {
            int temp = a;
            a = b;
            b = temp;
        }
        drawHLine(a, y, b - a + 1, c);
    }
}

void Canvas::drawCircle(int x, int y, int r, unsigned int c)
{
    int f = 1 - r;
    int ddF_x = 1;
    int ddF_y = -2 * r;
    int i = 0;
    int j = r;

    drawPixel(x, y + r, c);
    drawPixel(x, y - r, c);
    drawPixel(x + r, y, c);
    drawPixel(x - r, y, c);

    while(i < j) {
        if(f >= 0) {
            j--;
            ddF_y += 2;
            f += ddF_y;
        }
        i++;
        ddF_x += 2;
        f += ddF_x;
        drawPixel(x + i, y + j, c);
        drawPixel(x - i, y + j, c);
        drawPixel(x + i, y - j, c);
        drawPixel(x - i, y - j, c);
        drawPixel(x + j, y + i, c);
        drawPixel(x - j, y + i, c);
        drawPixel(x + j, y - i, c);
        drawPixel(x - j, y - i, c);
    }
}

void Canvas::fillCircle(int x, int y, int r, unsigned int c)
{
    drawVLine(x, y - r, 2 * r + 1, c);
    fillCircleHelper(x, y, r, 3, 0, c);
}

void Canvas::drawRoundRect(int x, int y, int w, int h, int r, unsigned int c)
{
    //Draw the four lines
    drawHLine(x + r, y, w - 2 * r, c);            //Top
    drawHLine(x + r, y + h - 1, w - 2 * r, c);    //Bottom
    drawVLine(x, y + r, h - 2 * r, c);            //Left
    drawVLine(x + w - 1, y + r, h - 2 * r, c);    //Right

    //Draw the four corners
    drawCircleHelper(x + r, y + r, r, 1, c);
    drawCircleHelper(x + w - r - 1, y + r, r, 2, c);
    drawCircleHelper(x + w - r - 1, y + h - r - 1, r, 4, c);
    drawCircleHelper(x + r, y + h - r - 1, r, 8, c);
}

void Canvas::fillRoundRect(int x, int y, int w, int h, int r, unsigned int c)
{
    //Draw the body
    fillRect(x + r, y, w - 2 * r, h, c);

    //Draw the four corners
    fillCircleHelper(x + w - r - 1, y + r, r, 1, h - 2 * r - 1, c);
    fillCircleHelper(x+r, y + r, r, 2, h - 2 * r - 1, c);
}

void Canvas::drawImage(Image *img, int x, int y)
{
    for (int j = 0; j < img->height(); j++) {
        for (int i = 0; i < img->width(); i++) {
            drawPixel(i + x, j + y, img->pixel(i, j));
        }
    }
}

int Canvas::drawChar(char c, Font *fnt, int x, int y)
{
    //Get the character glyph
    BitmapImage glyph = fnt->glyph(c);

    //Draw the character glyph
    drawImage(&glyph, x, y);

    //Return the width of the glyph
    return glyph.width();
}

void Canvas::drawString(const char *str, Font *fnt, int x, int y)
{
    int cursX = 0;
    int cursY = 0;

    while(*str != NULL) {
        //Check for a new line character
        if (*str == '\n') {
            //Go to a new line
            cursX = 0;
            cursY += fnt->height();

            //We're done for this character
            str++;
            continue;
        }

        //Check for a carriage return character
        if (*str == '\r') {
            //Ignore it, we're done for this character
            str++;
            continue;
        }

        //Draw the character
        cursX += drawChar(*str++, fnt, x + cursX, y + cursY);
    }
}

void Canvas::drawString(const char *str, Font *fnt, int x, int y, int w, int h)
{
    int cursX = 0;
    int cursY = 0;

    while(*str != NULL) {
        //Check for a new line character
        if (*str == '\n') {
            //Check if we can fit another line
            if ((cursY + 2 * fnt->height()) < h) {
                //Yes we can
                cursX = 0;
                cursY += fnt->height();

                //We're done for this character
                str++;
                continue;
            } else {
                //Nope, we can't print any more so return
                return;
            }
        }

        //Check for a carriage return character
        if (*str == '\r') {
            //Ignore it, we're done for this character
            str++;
            continue;
        }

        //Check for entire words first
        if ((*str > ' ') && (*str <= 0x7E)) {
            //Draw entire word on canvas with correct wrapping
            //int i = 0;
            int wlen;

            //Determine the length of the next word
            wlen = fnt->measureWord(str);

            //Will the length of the next word exceed the margins?
            if ((wlen + cursX) > w) {
                //Only do a newline if the word will fit on it
                if (wlen <= w) {
                    //Check if we can fit another line
                    if ((cursY + 2 * fnt->height()) < h) {
                        //Yes we can
                        cursX = 0;
                        cursY += fnt->height();
                    } else {
                        //Nope, we can't print any more so return
                        return;
                    }
                }
            }

            //Put just the word characters on the display up to the next non-whitespace character or the end of the string
            while ((*str > ' ') && (*str <= 0x7E)) {
                //Check if the character will fit on the screen
                if ((fnt->glyph(*str).width() + cursX) > w) {
                    //Check if we can fit another line
                    if ((cursY + 2 * fnt->height()) < h) {
                        //Yes we can
                        cursX = 0;
                        cursY += fnt->height();
                    } else {
                        //Nope, we can't print any more so return
                        return;
                    }
                }

                //Draw the character
                cursX += drawChar(*str++, fnt, x + cursX, y + cursY);
            }
        } else {
            //Check if the character will fit on the screen
            if ((fnt->glyph(*str).width() + cursX) > w) {
                //Check if we can fit another line
                if ((cursY + 2 * fnt->height()) < h) {
                    //Yes we can
                    cursX = 0;
                    cursY += fnt->height();
                } else {
                    //Nope, we can't print any more so return
                    return;
                }
            }

            //Draw the character
            cursX += drawChar(*str++, fnt, x + cursX, y + cursY);
        }
    }
}

int Canvas::width(void)
{
    return m_Width;
}

int Canvas::height(void)
{
    return m_Height;
}

void Canvas::drawCircleHelper(int x, int y, int r, unsigned int corner, unsigned int c)
{
    int f = 1 - r;
    int ddF_x = 1;
    int ddF_y = -2 * r;
    int i = 0;
    int j = r;

    while (i < j) {
        if (f >= 0) {
            j--;
            ddF_y += 2;
            f += ddF_y;
        }
        i++;
        ddF_x += 2;
        f += ddF_x;
        if (corner & 0x4) {
            drawPixel(x + i, y + j, c);
            drawPixel(x + j, y + i, c);
        }
        if (corner & 0x2) {
            drawPixel(x + i, y - j, c);
            drawPixel(x + j, y - i, c);
        }
        if (corner & 0x8) {
            drawPixel(x - j, y + i, c);
            drawPixel(x - i, y + j, c);
        }
        if (corner & 0x1) {
            drawPixel(x - j, y - i, c);
            drawPixel(x - i, y - j, c);
        }
    }
}

void Canvas::fillCircleHelper(int x, int y, int r, unsigned int corner, int delta, unsigned int c)
{
    int f = 1 - r;
    int ddF_x = 1;
    int ddF_y = -2 * r;
    int i = 0;
    int j = r;

    while (i < j) {
        if (f >= 0) {
            j--;
            ddF_y += 2;
            f += ddF_y;
        }
        i++;
        ddF_x += 2;
        f += ddF_x;

        if (corner & 0x1) {
            drawVLine(x + i, y - j, 2 * j + 1 + delta, c);
            drawVLine(x + j, y - i, 2 * i + 1 + delta, c);
        }
        if (corner & 0x2) {
            drawVLine(x - i, y - j, 2 * j + 1 + delta, c);
            drawVLine(x - j, y - i, 2 * i + 1 + delta, c);
        }
    }
}