[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; } } }