Serwis Edukacyjny
Nauczycieli
w I-LO w Tarnowie

Do strony głównej I LO w Tarnowie

Materiały dla uczniów liceum

  Wyjście       Spis treści       Wstecz       Dalej  

©2019 mgr Jerzy Wałaszek
I LO w Tarnowie

logo

Autor artykułu: mgr Jerzy Wałaszek

 

 

SDL2

Odcinki

Rozdziały:
    Instalacja
    Typy danych
    Grafika rastrowa
    Okno
    Kontekst graficzny
    Punkty
    Odcinki
    Figury
    Algorytm Bresenhama
    Wypełnianie figur
    Wypełnianie obszarów
    Wypełnianie obszarów algorytmem Smitha
    Przezroczystość
    Zdarzenia

     Interfejs SDL2 wg nazw
     Interfejs SDL2 wg kategorii
W rozdziale:
Rysowanie odcinków
Łamane
Podsumowanie

 

Rysowanie linii

Gdy opanowałeś już rysowanie pikseli, to kolejnym krokiem twojej kariery graficznej będzie rysowanie odcinków.

Odcinek (ang. line) składa się z ciągu pikseli leżących na ekranie obok siebie w taki sposób, iż dają wrażenie prostej linii ciągłej. Nie jest to definicja akademicka. Linie tak naprawdę będą odcinkami o końcach określonych współrzędnymi. W SDL2 mamy zadanie ułatwione przy kreśleniu prostych linii, ponieważ istnieje do tego celu odpowiednia funkcja:

SDL_RenderDrawLine(r,x1,y1,x2,y2)

r – wskaźnik struktury SDL_Renderer z kontekstem graficznym
x1,y1 –  współrzędne punktu startowego odcinka
x2,y2 – współrzędne punktu końcowego

Proponuję od razu wypróbować tę funkcję.


Utwórz w CodeBlocks projekt SDL2, wpisz do edytora poniższy tekst programu, skompiluj go i uruchom:

 

// Linie 1
//---------

#include <SDL.h>
#include <iostream>

using namespace std;

// Rozmiar okienka
const int W_W = 640;
const int W_H = 480;

int main(int argc, char* args[])
{
   // Inicjujemy SDL
  if(SDL_Init(SDL_INIT_VIDEO))
  {
    cout << "SDL_Init Error: " << SDL_GetError() << endl;
    return 1;
  }

  // Tworzymy okno
  SDL_Window * w = SDL_CreateWindow("Linie", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, W_W, W_H, 0);
  if(!w)
  {
    cout << "SDL_CreateWindow Error: " << SDL_GetError() << endl;
    SDL_Quit();
    return 2;
  }

  // Tworzymy kontekst graficzny
  SDL_Renderer * r = SDL_CreateRenderer(w,0,SDL_RENDERER_PRESENTVSYNC);
  if(!r)
  {
     cout << "SDL_CreateRenderer Error: " << SDL_GetError() << endl;
     SDL_DestroyWindow(w);
     SDL_Quit();
     return 3;
  }

  // Rysujemy linie przekątne
  SDL_SetRenderDrawColor(r,255,0,0,255); // kolor czerwony
  if(SDL_RenderDrawLine(r,0,0,W_W-1,W_H-1)) cout << SDL_GetError() << endl;;

  SDL_SetRenderDrawColor(r,0,255,0,255); // kolor zielony
  SDL_RenderDrawLine(r,W_W-1,0,0,W_H-1);

  // Uaktualniamy obraz
  SDL_RenderPresent(r);

  // Czekamy na zdarzenie SDL_QUIT
  SDL_Event e;
  while(1)
  {
    SDL_WaitEvent(&e);
    if(e.type == SDL_QUIT)
    {
        cout << "Program closed after " << e.quit.timestamp << " miliseconds" << endl << endl;
        break;
    }
  }

  // Usuwamy kontekst graficzny
  SDL_DestroyRenderer(r);

  // Usuwamy okno
  SDL_DestroyWindow(w);

  // Kończymy pracę z SDL2
  SDL_Quit();

  return 0;
}

Program nie powinien sprawiać kłopotów w zrozumieniu, gdyż jest podobny do programów przedstawionych w poprzednim rozdziale. Rysowane są tutaj dwie linie przekątne, jedna w kolorze czerwonym, druga zielona, po czym program czeka na zamknięcie przez użytkownika okna roboczego.


Kolejny program tworzy nieco ciekawszy efekt. Na całej szerokości okna wyznaczamy zadaną liczbę równoodległych punktów na dolnej i górnej krawędzi, po czym rysujemy odcinki z narożników okna do tych punktów.

 

// Linie 2
//---------

#include <SDL.h>
#include <iostream>

using namespace std;

// Rozmiar okienka
const int W_W = 640;
const int W_H = 480;

// Liczba punktów
const int N = 20;

int main(int argc, char* args[])
{
   // Inicjujemy SDL
  if(SDL_Init(SDL_INIT_VIDEO))
  {
    cout << "SDL_Init Error: " << SDL_GetError() << endl;
    return 1;
  }

  // Tworzymy okno
  SDL_Window * w = SDL_CreateWindow("Linie", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, W_W, W_H, 0);
  if(!w)
  {
    cout << "SDL_CreateWindow Error: " << SDL_GetError() << endl;
    SDL_Quit();
    return 2;
  }

  // Tworzymy kontekst graficzny
  SDL_Renderer * r = SDL_CreateRenderer(w,0,SDL_RENDERER_PRESENTVSYNC);
  if(!r)
  {
     cout << "SDL_CreateRenderer Error: " << SDL_GetError() << endl;
     SDL_DestroyWindow(w);
     SDL_Quit();
     return 3;
  }

  // Rysujemy linie z narożników do wyznaczonych punktów
  int i,x;

  for(i = 0; i < N; i++)
  {
    x = (i * W_W) / N;
    SDL_SetRenderDrawColor(r,255,0,0,255); // Czerwony
    SDL_RenderDrawLine(r,0,0,x,W_H-1);
    SDL_SetRenderDrawColor(r,0,0,255,255); // Niebieski
    SDL_RenderDrawLine(r,W_W-1,0,x,W_H-1);

    SDL_SetRenderDrawColor(r,0,255,0,255); // Zielony
    SDL_RenderDrawLine(r,0,W_H-1,x,0);
    SDL_SetRenderDrawColor(r,255,255,255,255); // Biały
    SDL_RenderDrawLine(r,W_W-1,W_H-1,x,0);
  }

  // Uaktualniamy obraz
  SDL_RenderPresent(r);

  // Czekamy na zdarzenie SDL_QUIT
  SDL_Event e;
  while(1)
  {
    SDL_WaitEvent(&e);
    if(e.type == SDL_QUIT)
    {
        cout << "Program closed after " << e.quit.timestamp << " miliseconds" << endl << endl;
        break;
    }
  }

  // Usuwamy kontekst graficzny
  SDL_DestroyRenderer(r);

  // Usuwamy okno
  SDL_DestroyWindow(w);

  // Kończymy pracę z SDL2
  SDL_Quit();

  return 0;
}

 


Wykorzystując pomysł z programu powyżej można uzyskać jeszcze ciekawszy efekt graficzny. W tym celu wyznaczamy równoodległe punkty na krawędziach poziomych i pionowych okna:

Łączymy odcinkiem pierwszy punkt na boku pionowym z pierwszym punktem na boku poziomym:

Następnie łączymy drugi punkt na boku pionowym z drugim punktem na boku poziomym:

 

Teraz trzeci punkt pionowy z trzecim punktem poziomym:

Operację kontynuujemy, aż połączymy ze sobą wszystkie punkty pionowe z poziomymi:

Poprzez symetrię taką samą operację wykonujemy na pozostałych parach boków pionowych i poziomych:

Gdy liczba punktów będzie odpowiednio duża, efekt stanie się ciekawy. A oto program:

 

// Linie 3
//---------

#include <SDL.h>
#include <iostream>

using namespace std;

// Rozmiar okienka
const int W_W = 640;
const int W_H = 480;

// Liczba punktów
const int N = 30;

int main(int argc, char* args[])
{
   // Inicjujemy SDL
  if(SDL_Init(SDL_INIT_VIDEO))
  {
    cout << "SDL_Init Error: " << SDL_GetError() << endl;
    return 1;
  }

  // Tworzymy okno
  SDL_Window * w = SDL_CreateWindow("Linie", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, W_W, W_H, 0);
  if(!w)
  {
    cout << "SDL_CreateWindow Error: " << SDL_GetError() << endl;
    SDL_Quit();
    return 2;
  }

  // Tworzymy kontekst graficzny
  SDL_Renderer * r = SDL_CreateRenderer(w,0,SDL_RENDERER_PRESENTVSYNC);
  if(!r)
  {
     cout << "SDL_CreateRenderer Error: " << SDL_GetError() << endl;
     SDL_DestroyWindow(w);
     SDL_Quit();
     return 3;
  }

  // Łączymy liniami punkty pionowe z poziomymi
  int i,x,y;

  for(i = 0; i < N; i++)
  {
    x = (i * (W_W - 1)) / (N - 1); // Punkt w poziomie
    y = (i * (W_H - 1)) / (N - 1); // Punkt w pionie
    SDL_SetRenderDrawColor(r,255,255,255,255); // Kolor biały
    SDL_RenderDrawLine(r,0,y,x,W_H-1);
    SDL_RenderDrawLine(r,W_W-1,y,W_W-1-x,W_H-1);
    SDL_RenderDrawLine(r,0,W_H-1-y,x,0);
    SDL_RenderDrawLine(r,W_W-1,W_H-y,W_W-x,0);
  }

  // Uaktualniamy obraz
  SDL_RenderPresent(r);

  // Czekamy na zdarzenie SDL_QUIT
  SDL_Event e;
  while(1)
  {
    SDL_WaitEvent(&e);
    if(e.type == SDL_QUIT)
    {
        cout << "Program closed after " << e.quit.timestamp << " miliseconds" << endl << endl;
        break;
    }
  }

  // Usuwamy kontekst graficzny
  SDL_DestroyRenderer(r);

  // Usuwamy okno
  SDL_DestroyWindow(w);

  // Kończymy pracę z SDL2
  SDL_Quit();

  return 0;
}

 


Kolejny program tworzy ciekawą grafikę, łącząc ze sobą punkty leżące na obwodzie elipsy wpisanej w ekran. Pokażę krok po kroku, jak należy podejść do tego od strony matematycznej.

Mamy okno o szerokości W_W (ang. window width) i wysokości W_H (ang. window height):

Na rysunku zaznaczone są również współrzędne punktów narożnych. W okno wpisujemy elipsę tak, aby stykała się z jego krawędziami:

Środek elipsy znajduje się w środku okna.

Na obwodzie elipsy wybieramy punkt P i prowadzimy od niego pomocniczy promień r do środka elipsy. Promień ten tworzy kąt α ze średnicą poziomą:

Parametry a i b są odpowiednio równe połowom średnic poziomej i pionowej. Punkt S jest środkiem elipsy. Do wyznaczenia współrzędnych punktu P wykorzystujemy równanie parametryczne elipsy:

Jeśli chcemy wyznaczyć ciąg n równoodległych punktów na obwodzie elipsy, to musimy w tym wzorze zmieniać kąt α z równym krokiem w zakresie od 0 do 2π. Obliczone współrzędne zapamiętujemy w odpowiednich tablicach:

Gdy wyznaczymy wszystkie punkty, łączymy je odcinkami. Wybieramy punkt pierwszy i łączymy go ze wszystkimi następnymi:

Przechodzimy do punktu drugiego i wykonujemy tę samą operację. Zwróć jednak uwagę, że nie musisz łączyć go z punktem pierwszym, ponieważ połączenie to już istnieje. Zatem wystarczy punkt drugi połączyć z trzecim, czwartym, itd. Zawsze punkt i-ty łączymy z punktami o numerach większych od i:

Gdy połączymy wszystkie punkty, otrzymamy następującą figurę:

Program jest następujący:

 
// Linie 4
//---------

#include <SDL.h>
#include <iostream>
#include <cmath>

using namespace std;

// Rozmiar okienka
const int W_W = 640;
const int W_H = 480;

// Liczba punktów
const int N = 24;

int main(int argc, char* args[])
{
   // Inicjujemy SDL
  if(SDL_Init(SDL_INIT_VIDEO))
  {
    cout << "SDL_Init Error: " << SDL_GetError() << endl;
    return 1;
  }

  // Tworzymy okno
  SDL_Window * w = SDL_CreateWindow("Linie", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, W_W, W_H, 0);
  if(!w)
  {
    cout << "SDL_CreateWindow Error: " << SDL_GetError() << endl;
    SDL_Quit();
    return 2;
  }

  // Tworzymy kontekst graficzny
  SDL_Renderer * r = SDL_CreateRenderer(w,0,SDL_RENDERER_PRESENTVSYNC);
  if(!r)
  {
     cout << "SDL_CreateRenderer Error: " << SDL_GetError() << endl;
     SDL_DestroyWindow(w);
     SDL_Quit();
     return 3;
  }

  // Tablice współrzędnych
  int x[N],y[N],i,j;

  // Współrzędne środka okna
  int sx = W_W / 2 - 1;
  int sy = W_H / 2 - 1;

  // Promienie elipsy
  int a = sx + 1;
  int b = sy + 1;

  // Obliczamy współrzędne punktów i zapamiętujemy je w tablicach
  for(i = 0; i < N; i++)
  {
    x[i] = sx + a * cos(i * 2 * M_PI / N);
    y[i] = sy + b * sin(i * 2 * M_PI / N);
  }

  // Rysujemy odcinki łączące punkty

  SDL_SetRenderDrawColor(r,255,255,255,255); // Kolor biały
  for(i = 0; i < N - 1; i++)
    for(j = i+1; j < N; j++)
      SDL_RenderDrawLine(r,x[i],y[i],x[j],y[j]);

  // Uaktualniamy obraz
  SDL_RenderPresent(r);

  // Czekamy na zdarzenie SDL_QUIT
  SDL_Event e;
  while(1)
  {
    SDL_WaitEvent(&e);
    if(e.type == SDL_QUIT)
    {
        cout << "Program closed after " << e.quit.timestamp << " miliseconds" << endl << endl;
        break;
    }
  }

  // Usuwamy kontekst graficzny
  SDL_DestroyRenderer(r);

  // Usuwamy okno
  SDL_DestroyWindow(w);

  // Kończymy pracę z SDL2
  SDL_Quit();

  return 0;
}

 


Teraz pobawimy się liniami kolorowymi.

Pierwszy program rysuje odcinki w przypadkowych kolorach. Współrzędne początków i końców odcinków również są losowane tak, aby wypadały w obszarze okna. Program działa do momentu aż zostanie zamknięty.

 
// Linie 5
//---------

#include <SDL.h>
#include <iostream>
#include <cstdlib>
#include <ctime>

using namespace std;

// Rozmiar okienka
const int W_W = 640;
const int W_H = 480;

int main(int argc, char* args[])
{
   // Inicjujemy SDL
  if(SDL_Init(SDL_INIT_VIDEO))
  {
    cout << "SDL_Init Error: " << SDL_GetError() << endl;
    return 1;
  }

  // Tworzymy okno
  SDL_Window * w = SDL_CreateWindow("Linie", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, W_W, W_H, 0);
  if(!w)
  {
    cout << "SDL_CreateWindow Error: " << SDL_GetError() << endl;
    SDL_Quit();
    return 2;
  }

  // Tworzymy kontekst graficzny
  SDL_Renderer * r = SDL_CreateRenderer(w,0,SDL_RENDERER_PRESENTVSYNC);
  if(!r)
  {
     cout << "SDL_CreateRenderer Error: " << SDL_GetError() << endl;
     SDL_DestroyWindow(w);
     SDL_Quit();
     return 3;
  }

  // Inicjujemy generator pseudolosowy
  srand(time(NULL));

  // Animacja
  SDL_Event e;
  int c = 0; // Licznik linii
  while(1)
  {
    // Losujemy kolor linii
    SDL_SetRenderDrawColor(r,rand(),rand(),rand(),255);

    // Rysujemy linię
    SDL_RenderDrawLine(r,rand()%W_W,rand()%W_H,rand()%W_W,rand()%W_H);

    // Uaktualniamy okno po narysowaniu 100 linii
    if(++c == 100)
    {
      c = 0;
      SDL_RenderPresent(r);
    }

    // Sprawdzamy, czy użytkownik nie zamyka programu
    if(SDL_PollEvent(&e) && e.type == SDL_QUIT) break;
  }
  // Usuwamy kontekst graficzny
  SDL_DestroyRenderer(r);

  // Usuwamy okno
  SDL_DestroyWindow(w);

  // Kończymy pracę z SDL2
  SDL_Quit();

  return 0;
}

 


Ciekawszy będzie następny program. Rysuje on animowane odcinki, które stopniowo wypełniają okno. Wykorzystujemy tutaj ruch końców odcinka w ten sam sposób, co przy odbijanych punktach z poprzedniego rozdziału. Kolor odcinka zmienia się płynnie w trakcie rysowania.

 
// Linie 6
//---------

#include <SDL.h>
#include <iostream>
#include <cstdlib>
#include <ctime>

using namespace std;

// Rozmiar okienka
const int W_W = 640;
const int W_H = 480;

int main(int argc, char* args[])
{
   // Inicjujemy SDL
  if(SDL_Init(SDL_INIT_VIDEO))
  {
    cout << "SDL_Init Error: " << SDL_GetError() << endl;
    return 1;
  }

  // Tworzymy okno
  SDL_Window * w = SDL_CreateWindow("Linie", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, W_W, W_H, 0);
  if(!w)
  {
    cout << "SDL_CreateWindow Error: " << SDL_GetError() << endl;
    SDL_Quit();
    return 2;
  }

  // Tworzymy kontekst graficzny
  SDL_Renderer * r = SDL_CreateRenderer(w,0,SDL_RENDERER_PRESENTVSYNC);
  if(!r)
  {
     cout << "SDL_CreateRenderer Error: " << SDL_GetError() << endl;
     SDL_DestroyWindow(w);
     SDL_Quit();
     return 3;
  }

  // Inicjujemy generator pseudolosowy
  srand(time(NULL));

  // Inicjujemy zmienne

  int x1 = rand() % W_W; // Punkt początkowy linii
  int y1 = rand() % W_H;
  int x2 = rand() % W_W; // Punkt końcowy linii
  int y2 = rand() % W_H;

  int dx1,dy1,dx2,dy2; // Przyrosty współrzędnych

  do dx1 = -3 + rand() %7; while(!dx1);
  do dy1 = -3 + rand() %7; while(!dy1);
  do dx2 = -3 + rand() %7; while(!dx2);
  do dy2 = -3 + rand() %7; while(!dy2);

  int cr = rand() % 256; // Składowa czerwona koloru linii
  int cg = rand() % 256; // Składowa zielona koloru linii
  int cb = rand() % 256; // Składowa niebieska koloru linii

  int dr,dg,db; // Przyrosty składowych koloru

  do dr = -3 + rand() %7; while(!dr);
  do dg = -3 + rand() %7; while(!dg);
  do db = -3 + rand() %7; while(!db);

  // Animacja
  SDL_Event e;
  while(1)
  {
    // Ustawiamy kolor linii
    SDL_SetRenderDrawColor(r,cr,cg,cb,255);

    // Modyfikujemy składowe koloru
    if((cr+dr > 255) || (cr+dr < 0)) dr = -dr;
    if((cg+dg > 255) || (cg+dg < 0)) dg = -dg;
    if((cb+db > 255) || (cb+db < 0)) db = -db;

    cr += dr;
    cg += dg;
    cb += db;

    // Rysujemy linię
    SDL_RenderDrawLine(r,x1,y1,x2,y2);

    // Modyfikujemy współrzędne linii
    if((x1+dx1 >= W_W) || (x1+dx1 < 0)) dx1 = -dx1;
    if((y1+dy1 >= W_H) || (y1+dy1 < 0)) dy1 = -dy1;
    if((x2+dx2 >= W_W) || (x2+dx2 < 0)) dx2 = -dx2;
    if((y2+dy2 >= W_H) || (y2+dy2 < 0)) dy2 = -dy2;

    x1 += dx1;
    y1 += dy1;
    x2 += dx2;
    y2 += dy2;

    // Uaktualniamy okno
    SDL_RenderPresent(r);

    // Sprawdzamy, czy użytkownik nie zamyka programu
    if(SDL_PollEvent(&e) && e.type == SDL_QUIT) break;
  }

  // Usuwamy kontekst graficzny
  SDL_DestroyRenderer(r);

  // Usuwamy okno
  SDL_DestroyWindow(w);

  // Kończymy pracę z SDL2
  SDL_Quit();

  return 0;
}

 

Szybkość działania tego programu ogranicza uaktualnianie treści okna przez funkcję SDL_RenderPresent(). Jeśli chcesz, aby odcinki były rysowane szybciej, zastosuj licznik wywołań jak w poprzednim programie. Same odcinki rysowane są sprzętowo bardzo szybko (kilkadziesiąt tysięcy na sekundę).

 


Odcinki mogą być wykorzystane do wypełniania obszarów gradientem, czyli wypełnieniem, które płynnie zmienia barwę z jednego koloru w drugi. Należy tu rozwiązać problem zmiany składowych koloru.

Mamy dwa punkty A i B leżące na linii poziomej (te same rozważania można przeprowadzić dla linii pionowej):

W punkcie A składowa czerwona r ma wartość rA, a w punkcie B ma ona wartość rB (to samo dotyczy dwóch pozostałych składowych koloru):

Chcemy wyznaczyć wartość tej składowej w poszczególnych pikselach odcinka AB. Najpierw wyznaczamy odległość w pikselach od punktu A do punktu B:

 

Punkt A ma współrzędną xA. Punkt B ma współrzędną xB (dla wariantu pionowego będą to współrzędne yA i yB). Zatem:

Odległość ta określa liczbę pikseli zawartą pomiędzy punktami A i B (z nimi włącznie), a więc również liczbę kroków zmiany składowej r. Będziemy przechodzić zmienną i przez poszczególne piksele od xA do xB. Zapiszmy:

Podobnie będzie się zmieniała składowa czerwona, lecz przyrosty mogą nie być równe 1 jak w przypadku współrzędnej x. Musimy zastosować wzór:

Jak ten wzór rozumieć? Gdy jesteśmy w punkcie xA, to i = 0. W takim przypadku składowa r przyjmuje wartość rA. Gdy jesteśmy w punkcie xB, to i ma wartość xB - xA i po wstawieniu tego do wzoru otrzymujemy składową r równą rB.  Dla punktu pomiędzy xA i xB składowa czerwona przyjmuje wartość pośrednią pomiędzy rA i rB.

Mamy wzór przeliczeniowy, możemy napisać program, który będzie go wykorzystywał do rysowania prostokątów wypełnionych poziomym gradientem.

 

// Linie 7
//---------

#include <SDL.h>
#include <iostream>
#include <cstdlib>
#include <ctime>

using namespace std;

// Rozmiar okienka
const int W_W = 640;
const int W_H = 480;

int main(int argc, char* args[])
{
   // Inicjujemy SDL
  if(SDL_Init(SDL_INIT_VIDEO))
  {
    cout << "SDL_Init Error: " << SDL_GetError() << endl;
    return 1;
  }

  // Tworzymy okno
  SDL_Window * w = SDL_CreateWindow("Linie", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, W_W, W_H, 0);
  if(!w)
  {
    cout << "SDL_CreateWindow Error: " << SDL_GetError() << endl;
    SDL_Quit();
    return 2;
  }

  // Tworzymy kontekst graficzny
  SDL_Renderer * r = SDL_CreateRenderer(w,0,SDL_RENDERER_PRESENTVSYNC);
  if(!r)
  {
     cout << "SDL_CreateRenderer Error: " << SDL_GetError() << endl;
     SDL_DestroyWindow(w);
     SDL_Quit();
     return 3;
  }

  // Inicjujemy generator pseudolosowy
  srand(time(NULL));

  // Zmienne
  int xA,yA,xB,yB,rA,rB,gA,gB,bA,bB,i,x,cr,cg,cb;

  // Animacja
  SDL_Event e;
  while(1)
  {
    // Losujemy lewy górny narożnik prostokąta
    xA = rand() % W_W;
    yA = rand() % W_H;

    // Losujemy prawy dolny narożnik prostokąta
    xB = rand() % W_W;
    yB = rand() % W_H;

    // Korygujemy współrzędne:
    if(xA > xB) swap(xA,xB);
    if(yA > yB) swap(yA,yB);
    if(xA == xB) xB++;

    // Losujemy kolory narożników
    rA = rand() % 256;
    gA = rand() % 256;
    bA = rand() % 256;
    rB = rand() % 256;
    gB = rand() % 256;
    bB = rand() % 256;

    // Rysujemy prostokąt liniami pionowymi

    for(i = xB - xA; i >= 0; i--)
    {
        x = xA + i;
        cr = rA + i * (rB - rA) / (xB - xA);
        cg = gA + i * (gB - gA) / (xB - xA);
        cb = bA + i * (bB - bA) / (xB - xA);
        SDL_SetRenderDrawColor(r,cr,cg,cb,255);
        SDL_RenderDrawLine(r,x,yA,x,yB);
    }

    // Uaktualniamy okno
    SDL_RenderPresent(r);

    // Krótkie opóźnienie
    SDL_Delay(50);

    // Sprawdzamy, czy użytkownik nie zamyka programu
    if(SDL_PollEvent(&e) && e.type == SDL_QUIT) break;
  }
  // Usuwamy kontekst graficzny
  SDL_DestroyRenderer(r);

  // Usuwamy okno
  SDL_DestroyWindow(w);

  // Kończymy pracę z SDL2
  SDL_Quit();

  return 0;
}

 

Jako ćwiczenie zmodyfikuj program tak, aby:

  • wypełniał prostokąty gradientami pionowymi,
  • wypełniał prostokąty gradientami pionowymi lub poziomymi wybieranymi losowo

 

Łamane

Łamana (ang. polyline) jest ciągiem odcinków połączonych ze sobą w ten sposób, iż koniec jednego staje się początkiem następnego:

Jeśli koniec ostatniego odcinka łączy się z początkiem pierwszego, otrzymujemy łamaną zamkniętą (ang. closed polyline):

Łamaną definiujemy za pomocą ciągu kolejnych punktów, wierzchołków łamanej, które są początkami i końcami jej odcinków. Łamaną można rysować za pomocą serii wywołań funkcji SDL_RenderDrawLine() lub, bardziej efektywnie, za pomocą pojedynczej funkcji SDL_RenderDrawLines(). Funkcja ta posiada następujące parametry:

SDL_RenderDrawLines(r,p,n)

r – wskaźnik struktury SDL_Renderer, która definiuje kontekst graficzny,
p – tablica struktur typu SDL_Point, która definiuje współrzędne kolejnych wierzchołków łamanej,
n – liczba wierzchołków w tablicy p, które zostaną połączone odcinkami.

Poniższy program rysuje łamaną zbudowaną z N wierzchołków, które są animowane w oknie. Do animacji wykorzystujemy odbijanie punktów od krawędzi okna, co opisaliśmy dokładnie w poprzednich programach.

 

// Łamane 1
//---------

#include <SDL.h>
#include <iostream>
#include <cstdlib>
#include <ctime>

using namespace std;

// Rozmiar okienka
const int W_W = 640;
const int W_H = 480;

// Liczba punktów łamanej
const int N = 40;

int main(int argc, char* args[])
{
   // Inicjujemy SDL
  if(SDL_Init(SDL_INIT_VIDEO))
  {
    cout << "SDL_Init Error: " << SDL_GetError() << endl;
    return 1;
  }

  // Tworzymy okno
  SDL_Window * w = SDL_CreateWindow("Linie", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, W_W, W_H, 0);
  if(!w)
  {
    cout << "SDL_CreateWindow Error: " << SDL_GetError() << endl;
    SDL_Quit();
    return 2;
  }

  // Tworzymy kontekst graficzny
  SDL_Renderer * r = SDL_CreateRenderer(w,0,SDL_RENDERER_PRESENTVSYNC);
  if(!r)
  {
     cout << "SDL_CreateRenderer Error: " << SDL_GetError() << endl;
     SDL_DestroyWindow(w);
     SDL_Quit();
     return 3;
  }

  // Inicjujemy generator pseudolosowy
  srand(time(NULL));

  // Tworzymy zmienne i inicjujemy je
  SDL_Point p[N+1];
  int dx[N],dy[N],i;

  // Losujemy współrzędne punktów i przyrosty
  for(i = 0; i < N; i++)
  {
    p[i].x = rand() % W_W;
    p[i].y = rand() % W_H;
    do dx[i] = -3 + rand() % 7; while(!dx[i]);
    do dy[i] = -3 + rand() % 7; while(!dy[i]);
  }

  // Ostatni punkt jest pierwszym, aby powstała łamana zamknięta

  p[N] = p[0];

  // Animacja
  SDL_Event e;
  while(1)
  {
    // Usuwamy treść poprzedniego okna
    SDL_SetRenderDrawColor(r,0,0,0,255);
    SDL_RenderClear(r);

    SDL_SetRenderDrawColor(r,255,255,255,255); // Kolor biały

    // Rysujemy łamaną
    SDL_RenderDrawLines(r,p,N+1);

    // Modyfikujemy współrzędne wierzchołków
    for(i = 0; i < N; i++)
    {
      if((p[i].x + dx[i] >= W_W) || (p[i].x + dx[i] < 0)) dx[i] = -dx[i];
      if((p[i].y + dy[i] >= W_H) || (p[i].y + dy[i] < 0)) dy[i] = -dy[i];
      p[i].x += dx[i];
      p[i].y += dy[i];
    }
    p[N] = p[0];  // Zamknięcie łamanej

    // Uaktualniamy okno
    SDL_RenderPresent(r);

    // Sprawdzamy, czy użytkownik nie zamyka programu
    if(SDL_PollEvent(&e) && e.type == SDL_QUIT) break;
  }

  // Usuwamy kontekst graficzny
  SDL_DestroyRenderer(r);

  // Usuwamy okno
  SDL_DestroyWindow(w);

  // Kończymy pracę z SDL2
  SDL_Quit();

  return 0;
}

 

Jeśli linie poruszają się zbyt szybko, dodaj opóźnienie za pomocą funkcji SDL_Delay().

 


Ciekawym zastosowaniem łamanych jest tworzenie wykresów funkcji matematycznych. Napiszemy program tworzący wykres dowolnej funkcji ciągłej w zadanym przedziale <xp,xk>. Wykres będzie uproszczony, niemniej użyteczny.

Oto procedura tworzenia wykresu:

Mamy daną funkcję y = f(x):

Chcemy utworzyć wykres tej funkcji w przedziale argumentów <xp,xk>:

Dzielimy przedział na N równoodległych punktów:

Przez dx oznaczamy tutaj odległość między dwoma sąsiednimi punktami. Jak policzyć wartość dx? Wystarczy zauważyć, że n punktów dzieli przedział na n-1 segmentów o długości dx:

n dx
2

3

4

...  
N

Gdy mamy obliczone dx, możemy  wyznaczyć każdy z punktów podziałowych:

Dla każdego z tych punktów obliczamy i zapamiętujemy wartość funkcji:

Otrzymujemy w ten sposób pary współrzędnych punktów wykresu:

Aby narysować wykres punkty te należy połączyć liniami, czyli utworzyć z nich łamaną. Ale najpierw musimy przeliczyć współrzędne wykresu na współrzędne okna graficznego. Zawrzyjmy cały wykres w prostokącie:

Lewy górny narożnik prostokąta ma współrzędne (xp, max y), gdzie przez max y rozumiemy największą wartość współrzędnej y punktów wykresy. Podobnie prawy dolny narożnik prostokąta ma współrzędne (xk,min y), gdzie min y oznacza najmniejszą wartość współrzędnych y punktów wykresu. Wartości max y i min y możemy obliczać w trakcie wyznaczania kolejnych yi.

Prostokąt odwzorujemy w obszar graficzny okna:

Co to znaczy odwzorować? Znaczy to: znaleźć wzór przeliczeniowy, który punkt P o współrzędnych (x,y) w obszarze prostokąta przetwarza w punkt P' o współrzędnych (xe,ye) w obszarze graficznym okna, przy czym narożniki prostokąta wykresu mają przechodzić w narożniki okna graficznego. Tworzymy proporcje:

Z proporcji wyliczamy:

Wzory te umożliwią nam przeliczenie współrzędnych punktów wykresu na współrzędne punktów wierzchołkowych łamanej w obszarze graficznym okna.

Pozostaje jeszcze problem narysowania osi OX i OY.  Oś OX narysujemy odcinkiem poziomym. Obliczamy współrzędną pionową tego odcinka wg wzoru:

Jeśli współrzędna ta wpada w obszar okna, to rysujemy odcinek poziomy od punktu (0,yOX) do punktu (W_W-1,yOX).

Podobnie postępujemy z osią OY. Wyliczamy jej współrzędną poziomą:

Jeśli wpada w obszar okna, to rysujemy odcinek pionowy od punktu (xOY,0) do punktu (xOY,W_H-1).

Mamy wszystko. Program jest następujący:

 

// Łamane 2
//---------

#include <SDL.h>
#include <iostream>
#include <cmath>

using namespace std;

// Rozmiar okienka
const int W_W = 640;
const int W_H = 480;

// Liczba punktów wykresu
const int N = 500;

// Tutaj wpisujesz wzór funkcji, której wykres program ma wykonać
//---------------------------------------------------------------
double f(double x)
{
    return x*sin(x*x)*cos(x*x*x);
}
// Tutaj określasz przedział dla wykresu
//--------------------------------------

double xp = -1; // Początek
double xk = 2;  // Koniec

int main(int argc, char* args[])
{
   // Inicjujemy SDL
  if(SDL_Init(SDL_INIT_VIDEO))
  {
    cout << "SDL_Init Error: " << SDL_GetError() << endl;
    return 1;
  }

  // Tworzymy okno
  SDL_Window * w = SDL_CreateWindow("Linie", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, W_W, W_H, 0);
  if(!w)
  {
    cout << "SDL_CreateWindow Error: " << SDL_GetError() << endl;
    SDL_Quit();
    return 2;
  }

  // Tworzymy kontekst graficzny
  SDL_Renderer * r = SDL_CreateRenderer(w,0,SDL_RENDERER_PRESENTVSYNC);
  if(!r)
  {
     cout << "SDL_CreateRenderer Error: " << SDL_GetError() << endl;
     SDL_DestroyWindow(w);
     SDL_Quit();
     return 3;
  }

  // Tworzymy zmienne
  double x[N],y[N],dx,maxy,miny;

  SDL_Point p[N];

  int i,xOY,yOX;

  // Obliczamy odległość między punktami podziałowymi
  dx = (xk - xp) / (N - 1);

  // Obliczamy kolejne punkty podziałowe, wartości funkcji w tych punktach oraz maxy i miny
  for(i = 0; i < N; i++)
  {
    x[i] = xp + i * dx;
    y[i] = f(x[i]);
    if(!i) maxy = miny = y[0];
    if(y[i] > maxy) maxy = y[i];
    if(y[i] < miny) miny = y[i];
  }

  // Przeliczamy współrzędne wykresu na współrzędne okna graficznego
  for(i = 0; i < N; i++)
  {
    p[i].x = (W_W - 1) * (x[i] - xp) / (xk - xp);
    p[i].y = (W_H - 1) * (maxy - y[i]) / (maxy - miny);
  }

  // Rysujemy osie wykresu
  SDL_SetRenderDrawColor(r,0,128,255,255); // Kolor niebieski
  xOY = - (W_W - 1) * xp / (xk - xp);
  if((xOY >= 0) && (xOY < W_W)) SDL_RenderDrawLine(r,xOY,0,xOY,W_H-1);
  yOX = (W_H - 1) * maxy / (maxy - miny);
  if((yOX >= 0) && (yOX < W_H)) SDL_RenderDrawLine(r,0,yOX,W_W-1,yOX);

  // Rysujemy linię wykresu
  SDL_SetRenderDrawColor(r,255,255,255,255); // Kolor biały
  SDL_RenderDrawLines(r,p,N);

  // Uaktualniamy treść okna
  SDL_RenderPresent(r);

  // Czekamy na zdarzenie SDL_QUIT
  SDL_Event e;
  while(1)
  {
    SDL_WaitEvent(&e);
    if(e.type == SDL_QUIT)
    {
        cout << "Program closed after " << e.quit.timestamp << " miliseconds" << endl << endl;
        break;
    }
  }

  // Usuwamy kontekst graficzny
  SDL_DestroyRenderer(r);

  // Usuwamy okno
  SDL_DestroyWindow(w);

  // Kończymy pracę z SDL2
  SDL_Quit();

  return 0;
}

 

 

Podsumowanie

SDL_SetRenderDrawColor(r,cr,cg,cb,ca) – ustawia kolor dla operacji graficznej.

r – wskaźnik struktury SDL_Renderer
cr – składowa czerwona
cg – składowa zielona
cb – składowa niebieska
ca – przezroczystość koloru

 

SDL_RenderDrawLine(r,x1,y1,x2,y2) – rysuje odcinek

r – wskaźnik struktury SDL_Renderer
x1,y1 – współrzędne punktu początkowego odcinka
x2,y2 – współrzędne punktu końcowego odcinka

 

SDL_RenderDrawLines(r,p,n) – rysuje łamaną

r – wskaźnik struktury SDL_Renderer
p – tablica struktur typu SDL_Point, która definiuje współrzędne kolejnych wierzchołków łamanej
n – liczba wierzchołków w tablicy p.

 

Zespół Przedmiotowy
Chemii-Fizyki-Informatyki

w I Liceum Ogólnokształcącym
im. Kazimierza Brodzińskiego
w Tarnowie
ul. Piłsudskiego 4
©2019 mgr Jerzy Wałaszek

Materiały tylko do użytku dydaktycznego. Ich kopiowanie i powielanie jest dozwolone
pod warunkiem podania źródła oraz niepobierania za to pieniędzy.

Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl

Serwis wykorzystuje pliki cookies. Jeśli nie chcesz ich otrzymywać, zablokuj je w swojej przeglądarce.
Informacje dodatkowe.