LinkedIn YouTube Facebook
Szukaj

Newsletter

Proszę czekać.

Dziękujemy za zgłoszenie!

Wstecz
Artykuły

[JAK OBIEKTOWO NAPISAĆ DRIVER] STM32NUCLEO + mbed.org + graficzny LCD = druga aplikacja na STM32 w sieciowym środowisku programistycznym

Przedstawiony w poprzednim artykule sposób implementacji sterownika wyświetlacza oferuje skromne możliwości i należałoby ją rozbudować o dodatkowe funkcje, m.in. do rysowania linii oraz prostokątów. Takie zadanie można zrealizować na dwa sposoby. Pierwszy, typowy dla klasycznego języka C, polega na zdefiniowaniu funkcji, których prototypy mogłyby wyglądać w sposób następujący:

void drawRectangle(int x, int y, int width, int height, unsigned int color, unsigned char style);
void drawLine(int x1, int y1, int x2, int y2, unsigned int color);

Drugi sposób, znany z programowania obiektowego (język C++), zamiast wartości konkretnych parametrów wykorzystuje obiekty:

void drawRectangle(const Rect& rect, unsigned int color, unsigned char fill);
void drawLine(const Line& line, unsigned int color);
void drawRectangle(int x, int y, int width, int height, unsigned int color, unsigned char style);
void drawLine(int x1, int y1, int x2, int y2, unsigned int color);

Jak można zauważyć funkcje zdefiniowane w ten drugi sposób są bardziej czytelne jednak wymagają zdefiniowania dwóch klas: Line oraz Rect. Klasa Line będzie zawierała pola typu int przechowujące położenie punktów początku i końca odcinka prostej, konstruktory za pomocą których będzie można utworzyć obiekt Line, a także funkcje umożliwiające przesunięcie oraz przeskalowanie linii. Klasa Rect będzie wyglądać podobnie z tą różnicą, że zamiast współrzędnych i wymiarów będzie przechowywać cztery podobiekty typu Line. W celu uzyskania bardziej przejrzystego projektu zdefiniujemy te klasy w pliku Graphics.h:

#ifndef GRAPHICS_H
#define GRAPHICS_H

class Line {
  public:
    int x1, y1;
    int x2, y2;
    
    Line();
    Line(int lx1, int ly1, int lx2, int ly2);

    Line operator*=(const double scaleFactor);
    void move(int dx, int dy);
    void moveTo(int lx, int ly);
};

class Rect {
  public:
    Line bottom, left, right, top;
    
    Rect(int lx, int ly, int lwidth, int lheight);
    
    Rect operator*=(const double scaleFactor);
    void move(int dx, int dy);
    void moveTo(int lx, int ly);
};

#endif /* GRAPHICS_H */

W pliku implementacyjnym Graphics.cpp definiujemy konstruktory oraz funkcje klas powyższych. W przypadku skalowania obu współrzędnych zastosujemy przeciążanie operatora „*=” (mnożenie z przypisaniem wartości wyniku).

Line::Line()
    : x1(0), y1(0), x2(0), y2(0) {
    // Konstruktor domyślny musi być zdefiniowany, ponieważ wymaga tego klasa-rodzic Rect, w której
    //  zmienne typu Line są podobiektami
}

Line::Line(int lx1, int ly1, int lx2, int ly2)
    : x1(lx1), y1(ly1), x2(lx2), y2(ly2) {
    // Po prostu inicjalizacja zmiennych
}

Line Line::operator*=(const double scaleFactor) {
    // Wymnożenie długości rzutów linii przez współczynnik scaleFactor
    x2 = ((double)(x2 - x1))*scaleFactor + x1;
    y2 = ((double)(y2 - y1))*scaleFactor + y1;
    
    return *this;
}

void Line::move(int dx, int dy) {
    // Przesunięcie linii o (dx,dy)
    x1 += dx;
    y1 += dy;
    
    x2 += dx;
    y2 += dy;
}

void Line::moveTo(int lx, int ly) {
    // Przesunięcie punktu początkowego linii (x1,y1) do punktu (lx,ly)
    x2 = lx + (x2 - x1);
    y2 = ly + (y2 - y1);
    
    x1 = lx;
    y1 = ly;
}

Rect::Rect(int lx, int ly, int lwidth, int lheight) {
    // Utworzenie czterech obiektów typu Line stanowiących prostokąt
    bottom = Line(lx, ly + lheight, lx + lwidth, ly + lheight);
    left = Line(lx, ly, lx, ly + lheight);
    right = Line(lx + lwidth, ly, lx + lwidth, ly + lheight);
    top = Line(lx, ly, lx + lwidth, ly);
}

Rect Rect::operator*=(const double scaleFactor) {
    // Skalowanie długości krawędzi prostokąta
    bottom *= scaleFactor;
    left *= scaleFactor;
    right *= scaleFactor;
    top *= scaleFactor;
    
    // Przesunięcie dwóch krawędzi prostokąta
    bottom.moveTo(left.x2, left.y2);
    right.moveTo(top.x2, top.y2);
    
    return *this;
}

void Rect::move(int dx, int dy) {
    // Przesunięcie prostokąta o (dx,dy)
    bottom.move(dx, dy);
    left.move(dx, dy);
    right.move(dx, dy);
    top.move(dx, dy);
}

void Rect::moveTo(int lx, int ly) {
    // Przesunięcie punktu początkowego prostokąta (x1,y1) (wraz z prostokątem) do punktu (lx,ly)
    left.moveTo(lx, ly);
    top.moveTo(lx, ly);

    bottom.moveTo(left.x2, left.y2);
    right.moveTo(top.x2, top.y2);
}

Funkcje rysujące linie oraz prostokąty (plik NokiaLCD.cpp) wyglądają jak poniżej:

void NokiaLCD::drawLine(const Line& line, unsigned int color) {
    int x1 = line.x1;
    int y1 = line.y1;
    int x2 = line.x2;
    int y2 = line.y2;
    
    // Wyznaczenie różnicy między współrzędnymi punktów
    int dx = x2 - x1;
    int dy = y2 - y1;

    // Rysuj punkt początkowy
    drawPixel(x1, y1, color);

    // Zmienną niezależną jest x
    if( (abs(dx) >= abs(dy)) && abs(dx) != 0){
        float a = (float) dy / (float) dx;  // Wyznaczenie współczynnika kierunkowego
        float b = y1 - a*x1;                // Wyznaczenie przesunięcia
        dx = (dx < 0) ? -1 : 1;             // W zależności od tego który punkt znajduje
                                            //  się "wyżej" rysowanie będzie odbywać się w
                                            //  kierunku dodatnich lub ujemnych wartości

        // Rysuj kolejne punkty, dopóki nie osiągnięto punktu x2
        while(x1 != x2){
            x1 += dx;
            drawPixel(x1, (a*x1 + b), color);
        }
    }
    // Zmienną niezależną jest y
    else{
        float a = (float) dx / (float) dy;  // Wyznaczenie współczynnika kierunkowego
        float b = x1 - a*y1;                // Wyznaczenie przesunięcia
        dy = (dy < 0) ? -1 : 1;

        // Rysuj kolejne punkty, dopóki nie osiągnięto punktu y2
        while(y1 != y2){
            y1 += dy;
            drawPixel((a*y1 + b), y1, color);
        }
    }
}

void NokiaLCD::drawRectangle(const Rect& rect, unsigned int color, unsigned char fill) {
    int x1 = rect.top.x1;
    int y1 = rect.top.y1;
    int x2 = rect.bottom.x2;
    int y2 = rect.bottom.y2;
    
    // Wyznaczenie dlugosci bokow
    int a = abs(x2 - x1);
    int b = abs(y2 - y1);
    
    switch(fill){
        // Rysuj pełny prostokąt z wypełnieniem zadanym kolorem
        case 1:{
            for(int i=0; i < a; i++){
                for(int j=0; j < b; j++){
                    drawPixel(x1+i, y1+j, color);
                }
            }
            break;
        }
        // Rysuj 4 boki prostokąta z zadanym kolorem
        default:{
            drawLine(rect.top,    color);
            drawLine(rect.bottom, color);
            drawLine(rect.left,   color);
            drawLine(rect.right,  color);
            break;
        }
    }
}
Autor: Jan Szemiet