Serwis Edukacyjny
Nauczycieli
w I-LO w Tarnowie

obrazek

Materiały dla uczniów liceum

  Wyjście       Spis treści       Wstecz       Dalej  

Autor artykułu: mgr Jerzy Wałaszek
Uaktualniono: 31.07.2022

©2023 mgr Jerzy Wałaszek
I LO w Tarnowie

Punkty

SPIS TREŚCI
Podrozdziały

Punkt

Punkt, piksel (ang. pixel = picture element) jest podstawowym elementem grafiki. Obraz zbudowany jest z siatki pikseli, którą nazywamy rastrem (ang. raster).

Raster posiada określoną liczbę kolumn i linii pikseli. Parametry te nazywamy rozdzielczością obrazu (ang. picture resolution). Typowe rozdzielczości obrazu na ekranie monitora to: 800 x 600, 1024 x 768, 1200 x 1024, 1920 x 1080 (rozdzielczość HD), itd.

Pierwszą umiejętnością w świecie grafiki komputerowej jest postawienie piksela o wybranym kolorze w odpowiednim miejscu na obrazie rastrowym. Jeśli zabierzemy się za to programowo na powierzchni graficznej, to zadanie stanie się dosyć skomplikowane, ponieważ musimy uwzględniać różne sposoby kodowania pikseli (o ile chcemy napisać uniwersalny program, który będzie działał w praktycznie każdym środowisku graficznym). Większość współczesnych kart graficznych koduje piksele za pomocą 32 bitów, a barwy składowe zajmują w tym kodzie po 8 bitów. Tak naprawdę twoja funkcja musi najpierw rozpoznać tryb pracy ekranu (paletowy lub RGB) i podjąć odpowiednie do tego trybu działanie.


Uruchom CodeBlocks, utwórz z szablonu nowy projekt sdl2, skopiuj do edytora poniższy program, skompiluj go i uruchom:

C++
// Stawianie punktu programowo
//----------------------------

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

using namespace std;

// Funkcja stawia piksel o zadanym kolorze na pozycji x,y obrazu
//--------------------------------------------------------------
void Plotxyc(SDL_Surface * s, int x, int y, Uint8 r,  Uint8 g,  Uint8 b,  Uint8 a)
{
  Uint32 color, * addr;

  // Obliczamy adres piksela
  addr = (Uint32 *)s->pixels + x + y * s->w;

  // Obliczamy kod piksela
  color  = (r << s->format->Rshift) & (s->format->Rmask);
  color |= (g << s->format->Gshift) & (s->format->Gmask);
  color |= (b << s->format->Bshift) & (s->format->Bmask);
  color |= (a << s->format->Ashift) & (s->format->Amask);

  // Umieszczamy piksel na obrazie

  * addr = color;
}

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

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

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

  // Powierzchnia graficzna okna
  SDL_Surface * s = SDL_GetWindowSurface(w);

  // Sprawdzamy, czy piksele są 32-bitowe. Jeśli nie, kończymy
  if(s->format->BitsPerPixel != 32)
  {
    cout << "Pixels not 32 bits in size" << endl;
    SDL_DestroyWindow(w);
    SDL_Quit();
    return 2;
  }
  // Blokujemy dostęp do powierzchni graficznej innym procesom
  SDL_LockSurface(s);

  // Na środku okna rysujemy kilka kolorowych punktów
  Plotxyc(s,W_W/2 - 5,W_H/2 - 5,255,0,0,255);      // Piksel czerwony
  Plotxyc(s,W_W/2 + 5,W_H/2 - 5,0,255,0,255);      // Piksel zielony
  Plotxyc(s,W_W/2 + 5,W_H/2 + 5,0,0,255,255);      // Piksel niebieski
  Plotxyc(s,W_W/2 - 5,W_H/2 + 5,255,255,255,255);  // Piksel biały, wszystkie składowe równe 255

  // Odblokowujemy dostęp do powierzchni
  SDL_UnlockSurface(s);

  // Uaktualniamy zawartość okna
  SDL_UpdateWindowSurface(w);

  // Czekamy 5 sekund
  SDL_Delay(5000);

  // Usuwamy okno
  SDL_DestroyWindow(w);

  // Zamykamy SDL2
  SDL_Quit();

  return 0;
}

Opiszemy działanie funkcji Plotxyc(). Reszta programu jest standardowa i nie powinieneś mieć problemu z jej zrozumieniem. Ostatecznie cofnij się do poprzedniego rozdziału.

W funkcji main() sprawdzamy, czy piksele w powierzchni graficznej okna są 32-bitowe. Jeśli nie, to kończymy, ponieważ funkcja Plotxyc() zakłada, że rozmiar piksela jest 32-bitowy.

Plotxyc(...) Funkcja otrzymuje w parametrach kolejno:
wskaźnik do struktury SDL_Surface okna
współrzędne x i y piksela
składowe koloru: r, g i b
przezroczystość a.
Uint32 color, * addr; Tworzymy dwie zmienne. Zmienna color będzie zawierała kod piksela do wstawienia na powierzchnię graficzną. Zmienna addr jest wskaźnikiem, czyli adresem piksela w pamięci komputera.
addr = (Uint32 *)s->pixels + x + y * s->w; Wykorzystując dane zawarte w strukturze SDL_Surface, obliczamy adres piksela. Pole pixels w strukturze SDL_Surface przechowuje adres początku bufora zawierającego piksele obszaru okna. W języku C++ wskaźniki posiadają typy. Kompilator wykorzystuje te typy do wyznaczania właściwych przesunięć. Na przykład, załóżmy, że mamy wskaźnik a, który wskazuje obiekt typu Uint32. Typ Uint32 oznacza liczbę binarną 32-bitową bez znaku. 32 bity oznacza 4 bajty. Komórki pamięci komputera są tradycyjnie 8-bitowe i adresy odnoszą się do pojedynczych komórek 8-bitowych. Zatem wskaźnik a zawiera adres pierwszego bajtu danej typu Uint32:

Jeśli dodasz do wskaźnika a 1, to nie otrzymasz adresu drugiego bajtu danej Uint32. Operacje na wskaźnikach są zawsze skalowane i dodanie 1 do wskaźnika a ustawi go na adres następnej danej Uint32:

Czyli faktycznie dodanie 1 do wskaźnika a zwiększy jego zwartość o 4, a nie o 1. Wskaźniki należy zatem rozumieć jako adresy określonych obiektów w pamięci. Zwiększanie/zmniejszanie wskaźnika powoduje ustawienie adresu innego obiektu typu, który wskaźnik wskazuje. Musisz to dobrze zrozumieć, aby poradzić sobie z operacjami na wskaźnikach.

W naszym programie najpierw dokonujemy rzutowania wskaźnika pixels (który wskazuje obiekty typu void, a więc bez określenia rozmiaru) na adres danej Uint32, ponieważ zakładamy, że piksele w obszarze graficznym są danymi typu Uint32. Gdy wykonane zostanie rzutowanie, kompilator będzie traktował wskaźnik pixels tak, jakby wskazywał on dane Uint32. Do adresu dodajemy przesunięcie x, co da nam adres piksela w pierwszej linii obrazu leżącego na pozycji x w obszarze graficznym. Kolejnym czynnikiem, który dodajemy do wskaźnika, jest przesunięcie y pomnożone przez liczbę pikseli w linii obrazu (czyli szerokość okna). Dzięki tej operacji otrzymamy adres piksela leżącego w linii y na pozycji x.

color  = (r << s->format->Rshift) & (s->format->Rmask); Ta operacja ustawia w kodzie piksela pole składowej czerwonej.
color |= (g << s->format->Gshift) & (s->format->Gmask); Do kodu piksela dołączamy pole składowej zielonej.
color |= (b << s->format->Bshift) & (s->format->Bmask); Dołączamy pole składowej niebieskiej.
color |= (a << s->format->Ashift) & (s->format->Amask); Dołączamy kanał alfa (tutaj niewykorzystywany, zwykle ustawiony na 255, co oznacza, iż piksel jest nieprzezroczysty).
* addr = color; Gdy skompletujemy pełen kod piksela, umieszczamy go w buforze pod wyliczonym adresem

Jak widzisz, programowe ustawianie pikseli w obszarze okna jest możliwe, lecz niezalecane, ponieważ musisz rozważać różne sposoby kodowania pikseli. Gdy wykonamy to samo zadanie za pomocą akceleratora, operacja stanie się dużo prostsza, ponieważ sprawy techniczne kodowania pikseli pozostawiamy do rozstrzygnięcia sprzętowi karty graficznej.


Poniższy program jest dokładnym odpowiednikiem poprzedniego, lecz wykorzystuje kontekst graficzny do rysowania pikseli. Jest to preferowany sposób tworzenia grafiki w SDL2. W dalszych programach skupimy się zatem na sprzętowym tworzeniu grafiki, ponieważ akcelerator zrobi to setki razy szybciej od procesora.

C++
// Stawianie punktu sprzętowe
//----------------------------

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

using namespace std;

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

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

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

  // Tworzymy kontekst graficzny przyspieszany sprzętowo
  SDL_Renderer * r = SDL_CreateRenderer(w,0,0);
  if(!r)
  {
    cout << "SDL_CreateRender Error: " << SDL_GetError() << endl;
    SDL_DestroyWindow(w);
    SDL_Quit();
    return 1;
  }

  // Na środku okna rysujemy kilka kolorowych punktów
  SDL_SetRenderDrawColor(r,255,0,0,255);     SDL_RenderDrawPoint(r,W_W/2 - 5,W_H/2 - 5);
  SDL_SetRenderDrawColor(r,0,255,0,255);     SDL_RenderDrawPoint(r,W_W/2 + 5,W_H/2 - 5);
  SDL_SetRenderDrawColor(r,0,0,255,255);     SDL_RenderDrawPoint(r,W_W/2 + 5,W_H/2 + 5);
  SDL_SetRenderDrawColor(r,255,255,255,255); SDL_RenderDrawPoint(r,W_W/2 - 5,W_H/2 + 5);

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

  // Czekamy 5 sekund
  SDL_Delay(5000);

  // Usuwamy kontekst graficzny
  SDL_DestroyRenderer(r);

  // Usuwamy okno
  SDL_DestroyWindow(w);

  // Zamykamy SDL2
  SDL_Quit();

  return 0;
}

Nowością w tym programie jest funkcja:

SDL_RenderDrawPoint(r,x,y)

r – wskaźnik do struktury SDL_Renderer
x,y – współrzędne piksela

Piksel rysowany jest w kolorze, który został ustawiony przez funkcję SDL_SetRenderColor(), opisaną w poprzednim rozdziale. Jeśli piksel został narysowany na teksturze okna, to funkcja zwraca kod 0. Inny kod świadczy o błędzie. Nasz program nie testuje błędów rysowania pikseli dla prostoty.

Na początek:  podrozdziału   strony 

Zamykanie okna

W przyszłych programach zastąpimy wywołanie funkcji SDL_Delay() obsługą zdarzeń. Zdarzeniami dokładnie zajmiemy się w osobnym rozdziale. Zdarzenie (ang. event) jest ogólnie czymś, co pojawia się w trakcie działania programu. Może być to jakieś działanie ze strony użytkownika (ruch myszką, kliknięcie przycisku myszki, naciśnięcie klawisza na klawiaturze, itp.) lub akcja ze strony systemu (zmiana wymiarów okna, zmiana skupienia, zamknięcie okna, itp.). Różne systemy w różny sposób obsługują występujące w nich zdarzenia. Jednak biblioteka SDL2 ma na celu ujednolicić to zadanie na różnych platformach, ponieważ po to właśnie ją stworzono. Dlatego SDL2 przechwytuje zdarzenia systemowe i tłumaczy je na standardowy sposób ich opisu w SDL2. Dzięki temu rozwiązaniu zdarzenia w programie współpracującym z SDL2 obsługiwane są w identyczny sposób na każdej platformie (no, prawie identycznie), dla której została stworzona biblioteka SDL2. To samo dotyczy również grafiki.

Funkcja SDL_Delay() wstrzymuje wykonywanie programu na określoną liczbę milisekund. Jednakże w trakcie wstrzymania nie są obsługiwane w programie żadne zdarzenia. Dlatego twoje okienko pojawia się na ekranie, lecz nic nie możesz z nim zrobić. Nie reaguje na działania myszką, naciśnięcia klawiszy, itp. Zmienimy to.

Podstawą obsługi zdarzeń jest unia SDL_Event.

Dla przypomnienia, unia w języku C++ jest strukturą, która w tym samym bloku pamięci może zawierać dane różnego typu. Aby zrozumieć różnicę pomiędzy strukturą a unią, rozważmy prosty przykład. Tworzymy strukturę, która zawiera 3 pola:

struct struktura
{
  char a;
  int b;
  double c;
};

W pamięci komputera struktura taka wygląda następująco:

Każde z pól a, b, c jest przechowywane osobno w pamięci i możesz umieszczać w nich jednocześnie różne dane.

Z unią jest nieco inaczej. Jeśli stworzymy unię:

union unia
{
  char a;
  int b;
  double c;
};

to w pamięci będzie ona wyglądała następująco:

Pola zajmują w pamięci ten sam adres i w danej chwili możesz przechowywać tylko jedną daną. Jeśli do innego pola wprowadzisz inną daną, to nadpisze ona częściowo daną przechowywaną poprzednio. Unia nadaje się do przechowywania różnych danych, lecz w danej chwili może przechowywać tylko jedną z nich.


Uruchom CodeBlocks, utwórz projekt konsoli, przekopiuj do edytora poniższy program, skompiluj i uruchom go:

C++
// Struktura i unia

#include <iostream>

using namespace std;

int main()
{
  struct
  {
    char   a;
    int    b;
    double c;
  } struktura;

  union
  {
    char   a;
    int    b;
    double c;
  } unia;

  struktura.a = 'A';
  struktura.b = 199999;
  struktura.c = 3.1415;

  unia.a = 'A';
  unia.b = 199999;
  unia.c = 3.1415;

  cout << "STRUKTURA:" << endl
       << "POLE a = " << struktura.a << endl
       << "POLE b = " << struktura.b << endl
       << "POLE c = " << struktura.c << endl << endl
       << "UNIA:" << endl
       << "POLE a = " << unia.a << endl
       << "POLE b = " << unia.b << endl
       << "POLE c = " << unia.c << endl << endl;
  return 0;
}

Wynik programu jest następujący:

STRUKTURA:
POLE a = A
POLE b = 199999
POLE c = 3.1415

UNIA:
POLE a = o
POLE b = -1065151889
POLE c = 3.1415

W programie tworzymy strukturę i unię o takich samych polach. W strukturze i w unii zapisujemy w kolejnych polach te same dane, po czym wyświetlamy zawartość struktury i unii. Jak widzisz, struktura przechowała wszystkie dane tak, jak je wprowadzono, ponieważ pola struktury zajmują w pamięci osobne miejsca. W przypadku unii jedynie dana wprowadzona jako ostatnia zachowała swoją wartość. Poprzednie dane uległy uszkodzeniu, ponieważ pola unii zajmują w pamięci to samo miejsce i zostały nadpisane.

Unia SDL_Event jest unią różnych struktur opisujących rodzaj zdarzenia, które wystąpiło w systemie. Nie będę tutaj opisywał całości, ponieważ zrobię to w osobnym rozdziale.

Unia SDL_Event zawiera dwa elementy: pole type oraz pole będące strukturą zdarzenia:

union SDL_Event
{
  Uint32 type;
  struktura zdarzenia;
};

Pola te nakładają się na siebie. W każdej strukturze zdarzenia jest również pole type, które odpowiada dokładnie polu type unii SDL_Event. W polu tym SDL2 umieszcza stałą, która definiuje rodzaj zdarzenia, a zatem i resztę pól struktury. Każde zdarzenie posiada swoją własną strukturę.

Obsługa zdarzeń w SDL2 wygląda w skrócie tak:

Gdy wystąpi zdarzenie, jest ono umieszczane w kolejce zdarzeń (ang. event queue). Pozostaje tam dotąd, aż twój program odczyta je za pomocą jednej z funkcji:

SDL_WaitEvent(e) – czeka aż w kolejce pojawi się zdarzenie, pobiera je i umieszcza jego strukturę w unii SDL_Event wskazanej adresem e. W przypadku błędu zwraca 0, inaczej zwraca 1 jako wynik.
SDL_PollEvent(e) – jeśli w kolejce znajduje się zdarzenie, to zostanie pobrane i jego struktura umieszczona w unii SDL_Event wskazanej adresem e. W takim przypadku zwraca wynik 1. Jeśli w kolejce nie ma zdarzenia, to zwraca 0.

Różnica między tymi funkcjami jest taka, iż SDL_PollEvent() nie czeka na zdarzenie, lecz czyta to, co znajdzie w kolejce. Jeśli nic nie znajdzie, to wraca z wynikiem 0. Pozwala to programowi wykonywać inne operacje między zdarzeniami. Istotne to jest np. przy animacjach.

Gdy unia SDL_Event zostanie wypełniona strukturą zdarzenia, sprawdzasz jej typ i podejmujesz odpowiednie działania, co nazywamy obsługą zdarzenia.

Fragment kodu obsługującego zdarzenia jest następujący:

SDL_Event e;         // Unia
bool running = true;
while(running)
{
  SDL_WaitEvent(&e); // Czekamy na zdarzenie
  switch(e.type)
  {
   case ...          // Obsługa zdarzenia
  }
}

lub

SDL_Event e;
bool running = true;
while(running)
{
  while(SDL_PollEvent(&e)) // Dopóki kolejka zawiera zdarzenia, pobieramy je
    switch(e.type)
    {
       case ...            // Obsługa zdarzenia
    }
}

Oba kody są sobie równoważne.

W naszych programach będziemy reagowali na zdarzenie zamknięcia okna. Gdy użytkownik zamknie okno w kolejce zdarzeń pojawi się zdarzenie SDL_QuitEvent. Jego struktura jest następująca:

struct SDL_QuitEvent
{
  Uint32 type;
  Uint32 timestamp;
} quit;

W polu type znajduje się wartość SDL_QUIT. Pole timestamp zawiera liczbę milisekund działania programu. Dostęp do pola timestamp odbywa się poprzez nazwę struktury quit.

Zobaczmy, jak to działa w praktyce.


Uruchom CodeBlocks. Utwórz z szablonu projekt sdl2. Skopiuj do edytora poniższy kod, skompiluj i uruchom go:

C++
// Zdarzenie SDL_QUIT

#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[])
{

  // Okno robocze
  SDL_Window * w = NULL;

  // Inicjujemy SDL
  if(SDL_Init(SDL_INIT_VIDEO))
  {
    cout << "SDL_Init Error: " << SDL_GetError() << endl;
    return 1;
  }

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

  // 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 okno
  SDL_DestroyWindow(w);

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

  return 0;
}

Program w standardowy sposób tworzy okno. Następnie w pętli czeka aż wystąpi zdarzenie SDL_QUIT. Wtedy wypisuje czas wykonywania się i kończy działanie.

Zwróć uwagę, że teraz okienko można przesuwać po ekranie oraz minimalizować na pasek zadań. To właśnie jest zaletą obsługi zdarzeń.

Gdy mamy już rozwiązany problem zamykania okna w odpowiedzi na działania użytkownika, wróćmy do naszych pikseli.

Na początek:  podrozdziału   strony 

Stawianie pikseli

Zabawa sprawia, że nauka staje się bardziej efektywna. Pobawmy się zatem poznanymi dotąd funkcjami. Zachęcam cię, abyś samodzielnie również poeksperymentował.

Pierwszy program wypełnia okno pikselami o przypadkowym kolorze i położeniu. Działa do momentu aż zamkniesz okno.

C++
// Punkty 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;

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("Grafika", 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,0);
  if(!r)
  {
     cout << "SDL_CreateRenderer Error: " << SDL_GetError() << endl;
     SDL_DestroyWindow(w);
     SDL_Quit();
     return 3;
  }

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

  // W pętli rysujemy przypadkowe punkty i czekamy na zdarzenie SDL_QUIT
  SDL_Event e;
  int x,y,cr,cg,cb,c = 0;;
  while(1)
  {
    cr = rand();
    cg = rand();
    cb = rand();
    x  = rand() % W_W;
    y  = rand() % W_H;

    SDL_SetRenderDrawColor(r,cr,cg,cb,255);
    SDL_RenderDrawPoint(r,x,y);
    
    c++;
    if(c == 1000)
    {
      c = 0;
      SDL_RenderPresent(r);
    }
    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;
}

Wyjaśnijmy nowe elementy:

#include <cstdlib>
#include <ctime>
W programie wykorzystujemy liczby pseudolosowe, których generacja wymaga dołączenia tych dwóch plików nagłówkowych. Plik nagłówkowy cstdlib definiuje standardowe funkcje biblioteki C. Plik nagłówkowy ctime definiuje funkcje biblioteki C odnoszące się do czasu.
srand(time(NULL)); Liczby pseudolosowe wyglądają jak losowe (przypadkowe) lecz są tworzone programowo przez funkcję, którą nazywamy generatorem liczb pseudolosowych. Metod tworzenia liczb pseudolosowych wymyślono dużo. Powinieneś o tych liczbach dowiedzieć się na kursie programowania. Ich własnością jest to, że każda kolejna liczba pseudolosowa powstaje z poprzedniej wg określonego wzoru. Wynika z tego, że tworzą one zawsze ten sam ciąg kolejnych wartości, który po pewnym czasie zaczyna się powtarzać. Pierwsza liczba pseudolosowa, z której powstają wszystkie kolejne, nosi nazwę ziarna pseudolosowego (ang. pseudorandom seed). Gdy program jest uruchamiany ziarno otrzymuje zawsze tę samą wartość początkową. Funkcja srand() ustawia ziarno pseudolosowe na inną wartość, dzięki temu możemy otrzymać ciąg pseudolosowy rozpoczynający się w innym miejscu. Jeśli jako parametru tej funkcji użyjemy funkcji czasu time(), to zwróci ona wartość związaną z czasem, która zmienia się co 1 sekundę. W efekcie ziarno pseudolosowe zostanie zainicjowane inną wartością przy każdym uruchomieniu programu i wygenerowany ciąg pseudolosowy będzie inny.

Tutaj nie ma to wielkiego znaczenia, ale wyobraź sobie, że tworzysz grę karcianą i chciałbyś, aby przy każdym uruchomieniu programu gracz otrzymywał inne karty, inaczej przestałoby to być po chwili zabawne, nieprawdaż? Dlatego w programach wykorzystujących generator pseudolosowy wywołuje się funkcję srand() z parametrem time(NULL) (funkcja time() wypełnia informacją o czasie strukturę, której adres otrzyma jako parametr. Jeśli otrzyma adres zerowy NULL, to nic nie będzie wypełniać, zwróci jedynie wartość czasu). Należy to zrobić tylko jeden raz gdzieś na początku programu.

while(1) ... To jest tzw. pętla nieskończona (ang. infinitive loop). Pętla typu while wykonuje się do momentu aż jej argument przyjmie wartość 0. 1 nie zmieni się w 0. Zatem pętla będzie się ciągle wykonywać aż ją przerwiemy z jej wnętrza. Pętle nieskończone często stosuje się w programowaniu wtedy, gdy nie wiemy (lub nie chcemy określać), ile razy pętla ma się wykonać. Tutaj pętla będzie wykonywana aż użytkownik zamknie okno.
rand() Wywołanie tej funkcji zwraca liczbę pseudolosową w zakresie od 0 do RAND_MAX. Dla CodeBlocks jest to zakres 0...32767. Nie oznacza to, że nie można wygenerować większych liczb pseudolosowych. Nam tutaj jednak taki zakres zupełnie wystarczy.

Jeśli chcesz otrzymać liczbę pseudolosową w mniejszym zakresie od A do B, to stosujesz wzór:

liczba pseudolosowa od A do B = A + rand() % (B - A + 1)

W szczególności, jeśli A = 0, to:

liczba pseudolosowa od 0 do B = rand() % (B + 1)
cr = rand();
cg = rand();
cb = rand();
Losujemy składowe kolorów, Później zostaną one obcięte do rozmiaru 8-bitów.
x = rand() % W_W;
y = rand() % W_H;
Losujemy współrzędne x i y punktów, tak aby znajdowały się w obszarze okna.
SDL_SetRenderDrawColor(r,cr,cg,cb,255);
SDL_RenderDrawPoint(r,x,y);
Ustawiamy kolor piksela i rysujemy go w oknie na współrzędnych x,y.
c++;
 if(c == 1000)
{
  c = 0;
  SDL_RenderPresent(r);
}
Co 1000 narysowanych punktów przesyłamy teksturę okna do bufora ekranu. W ten sposób narysowane punkty stają się widoczne. Do liczenia punktów używamy licznika w zmiennej c. Chodzi tutaj o to, iż uaktualnienie treści okna zajmuje pewien czas i robienie tego przy każdym narysowanym pikselu byłoby dosyć wolne (możesz to sobie sprawdzić). Dzięki temu rozwiązaniu okno uaktualniamy hurtem po narysowaniu 1000 pikseli.
if(SDL_PollEvent(&e) && e.type == SDL_QUIT) break; Sprawdzamy, czy w kolejce znajduje się jakieś zdarzenie. Jeśli tak, to pobieramy jego strukturę do unii e i sprawdzamy, czy mamy do czynienia ze zdarzeniem SDL_QUIT. Jeśli tak, to przerywamy pętle, co spowoduje przejście wykonania do końcowej części programu.

Następny program rysuje w oknie ruchome punkty, które odbijają się od jego krawędzi. Ruch uzyskamy przez cykliczne rysowanie obrazu punktów i uaktualnianie tego obrazu w buforze ekranu.

Jak rysować odbijający się punkt? Musimy operować na współrzędnych tego punktu. W tym celu zapamiętujemy cztery informacje:

x,y – współrzędne punktu w obszarze okna
dx, dy – przyrosty współrzędnych, które określają położenie punktu w nowym kadrze.

Zasada jest następująca:

  1. Rysujemy punkt na pozycji x,y.
  2. Sprawdzamy, czy dodanie do x przyrostu dx spowoduje wyjście punktu poza obszar okna. Jeśli tak, to zmieniamy przyrost dx na przeciwny.
  3. To samo robimy z y i przyrostem dy.
  4. Dodajemy przyrosty do współrzędnych, otrzymując nowe położenie punktu.

Aby było ciekawiej w programie będziemy animować 1000 takich punktów.

W projekcie sdl2 przekopiuj do edytora poniższy program, skompiluj go i uruchom:

C++
// Punkty 2
//---------

#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
const int N = 1000;

int main(int argc, char* args[])
{

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

  // Tablice przyrostów
  int dx[N],dy[N];

  // Tablice składowych koloru
  Uint8 cr[N],cg[N],cb[N];

  // Inicjujemy SDL
  if(SDL_Init(SDL_INIT_VIDEO))
  {
    cout << "SDL_Init Error: " << SDL_GetError() << endl;
    return 1;
  }

  // Tworzymy okno
  SDL_Window * w = SDL_CreateWindow("Punkty", 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,0);
  if(!r)
  {
     cout << "SDL_CreateRenderer Error: " << SDL_GetError() << endl;
     SDL_DestroyWindow(w);
     SDL_Quit();
     return 3;
  }

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

  // Ustalamy wartości początkowe

  for(int i = 0; i < N; i++)
  {
      x[i] = rand() % W_W;
      y[i] = rand() % W_H;
      do dx[i] = -3 + (rand() % 7); while(!dx[i]);
      do dy[i] = -3 + (rand() % 7); while(!dy[i]);
      do cr[i] = rand(); while(!cr[i]);
      do cg[i] = rand(); while(!cg[i]);
      do cb[i] = rand(); while(!cb[i]);
  }

  SDL_Event e;
  while(1)
  {
    // Kasujemy poprzednio narysowane punkty
    SDL_SetRenderDrawColor(r,0,0,0,255);
    SDL_RenderClear(r);

    for(int i = 0; i < N; i++)
    {
        // Rysujemy punkt
        SDL_SetRenderDrawColor(r,cr[i],cg[i],cb[i],255);
        SDL_RenderDrawPoint(r,x[i],y[i]);

        // Wyliczamy nową pozycję
        if((x[i] + dx[i] >= W_W) || (x[i] + dx[i] < 0)) dx[i] = -dx[i];
        x[i] += dx[i];
        if((y[i] + dy[i] >= W_H) || (y[i] + dy[i] < 0)) dy[i] = -dy[i];
        y[i] += dy[i];
    }

    // Drobne opóźnienie
    SDL_Delay(30);

    // Uaktualniamy zawartość okna na ekranie
    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;
}

Wyjaśnijmy nowe elementy w programie:
const int N = 1000; Ta stała określa liczbę animowanych punktów. Możesz ją sobie zwiększyć lub zmniejszyć. Jednak przy przekroczeniu pewnej liczby punktów treść okna zaczyna mrugać, jeśli masz monitor LCD. Nie jest to wada programu, tylko właśnie monitora LCD. Jeśli obraz składa się z dużej liczby punktów różniących się znacznie jasnością, to niektóre monitory LCD powodują mruganie treści okna. Monitory kineskopowe nie wykazują tego efektu. Musisz sam poeksperymentować.
int x[N],y[N];
int dx[N],dy[N];
Uint8 cr[N],cg[N],cb[N];
Dane dla punktów przechowuje kilka tablic:
x,y – współrzędne punktów
dx,dy – przyrosty współrzędnych
cr,cg,cb – składowe kolorów punktów
x[i] = rand() % W_W;
y[i] = rand() % W_H;
W pierwszej pętli for inicjujemy komórki tablic odpowiednimi wartościami. Tutaj dla każdego punktu losowane jest położenie w oknie.
do dx[i] = -3 + (rand() % 7); while(!dx[i]);
do dy[i] = -3 + (rand() % 7); while(!dy[i]);
Te dwie pętle losują przyrosty: -3,-2,-1,1,2,3. Wartość 0 jest pomijana, ponieważ nie zmieniałaby położenia punktu. Dlatego losowanie odbywa się w pętli warunkowej, z której wyjście następuje, gdy wylosowany przyrost jest różny od 0.
do cr[i] = rand(); while(!cr[i]);
do cg[i] = rand(); while(!cg[i]);
do cb[i] = rand(); while(!cb[i]);
Na podobnej zasadzie losujemy składowe koloru, eliminując wartości 0.
while(1)... Animacja wykonywana jest w pętli nieskończonej, z której wyjście następuje po wykryciu zdarzenia SDL_QUIT.
SDL_SetRenderDrawColor(r,0,0,0,255);
SDL_RenderClear(r);
Ustawiamy kolor czarny i wywołujemy funkcję SDL_RenderClear(), która wypełni tym kolorem całe okno. Sprawdź, co się stanie, gdy usuniesz z programu wywołanie tej funkcji (umieść je w komentarzu).
for(int i = 0; i < N; i++)... Pętla służy do przeglądania komórek tablic przechowujących informacje o punktach.
SDL_SetRenderDrawColor(r,cr[i],cg[i],cb[i],255);
SDL_RenderDrawPoint(r,x[i],y[i]);
Ustawiamy kolor punktu z tablic składowych koloru i rysujemy w tym kolorze punkt na współrzędnych odczytanych z tablic x i y.
if((x[i] + dx[i] >= W_W) || (x[i] + dx[i] < 0)) dx[i] = -dx[i]; Tutaj sprawdzamy, czy po dodaniu do bieżącej współrzędnej przyrostu współrzędna ta nie wyjdzie poza granice okna. Jeśli tak, to zmieniamy znak przyrostu na przeciwny.
x[i] += dx[i]; Do współrzędnej dodajemy jej przyrost. W efekcie w następnym obiegu pętli while punkt pojawi się w innym miejscu obszaru graficznego okna.
if((y[i] + dy[i] >= W_H) || (y[i] + dy[i] < 0)) dy[i] = -dy[i];
y[i] += dy[i];
To samo robimy z drugą współrzędną. Ponieważ zmianie ulegają obie współrzędne punktu, będzie się on poruszał po linii ukośnej.
SDL_Delay(30); Rysowanie punktów przebiega bardzo szybko, więc wprowadzamy nieco opóźnienia. Tutaj też możesz poeksperymentować z różnymi czasami opóźnień.
SDL_RenderPresent(r); Operacje były wykonywane na teksturze w pamięci VRAM. Przesyłamy zawartość tekstury do bufora ekranowego, aby uaktualnić treść okna.
if(SDL_PollEvent(&e) && e.type == SDL_QUIT) break; Pobieramy z kolejki zdarzenie do unii SDL_Event i sprawdzamy, czy jest to zdarzenie SDL_QUIT. Jeśli tak, przerywamy pętlę nieskończoną while, a to powoduje zakończenie programu.


Trzeci program tworzy tzw. gradient, czyli płynne przejścia kolorów. Rysowane punkty mają kolor zależny od położenia na ekranie oraz od kolorów punktów w dwóch narożnikach okna: lewym górnym i prawym dolnym. Kolory narożników są płynnie zmieniane wg tej samej zasady, na której oparto poprzedni program. Do wartości składowych kolorów dodawane są przyrosty. Jeśli po dodaniu kolor wykraczałby poza dozwolony zakres od 0 do 255, to przyrost przed dodaniem ma zmieniany znak. Dzięki temu kolory składowe oscylują w górę i w dół pomiędzy wartościami granicznymi 0 i 255 i otrzymujemy różne kolory pośrednie, które płynnie się zmieniają.

Pozostaje problem wyznaczenia koloru punktu wewnątrz okna. Zastosowałem tutaj bardzo proste rozwiązanie: składowe koloru punktu zależą od składowych kolorów narożników oraz pozycji punktu.

Rozważmy najpierw przypadek uproszczony:

Punkt P porusza się od punktu A do punktu B po linii prostej. Odległość od punktu A do punktu P oznaczyliśmy literką d. Odległość między punktami A i B oznaczyliśmy literkami dd. W punkcie A wartość w wynosi a. W punkcie B wartość w wynosi b Ile wyniesie ta wartość w punkcie P, jeśli zmienia się liniowo wzdłuż drogi od punktu A do punktu B?

To proste zadanie geometryczne:

Dla d = 0 wartość w = a.
Dla d = dd wartość w = b.

W punkcie P mamy z proporcji:

W naszym programie jako odległość d weźmiemy sumę współrzędnych x i y punktu P. Jako dd weźmiemy sumę szerokości i wysokości okna pomniejszoną o 2 (dlaczego?). Na tej podstawie policzymy wartość składowej koloru w, jeśli a jest składową koloru punktu A, a b jest składową koloru punktu B:

Skopiuj do edytora poniższy program, skompiluj go i uruchom:

C++
// Punkty 3
//---------

#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("Punkty", 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,0);
  if(!r)
  {
     cout << "SDL_CreateRenderer Error: " << SDL_GetError() << endl;
     SDL_DestroyWindow(w);
     SDL_Quit();
     return 3;
  }

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

  // Ustalamy wartości początkowe
  int ce[2][3],cd[2][3],c[3],dc[3],i,j,x,y,d,dd,nc;

  for(i = 0; i < 2; i++)
    for(j = 0; j < 3; j++)
    {
      ce[i][j] = rand() % 256;
      do cd[i][j] = -2 + rand() % 5; while(!cd[i][j]);
    }

  dd = W_W + W_H - 2;

  SDL_Event e;
  while(1)
  {
    // Wyliczamy różnice kolorów
    for(i = 0; i < 3; i++)
      dc[i] = ce[1][i] - ce[0][i];

    // Rysujemy punkty
    for(x = 0; x < W_W; x++)
      for(y = 0; y < W_H; y++)
      {
        d = x + y;
        for(i = 0; i < 3; i++)
          c[i] = ce[0][i] + (dc[i] * d) / dd;
        SDL_SetRenderDrawColor(r,c[0],c[1],c[2],255);
        SDL_RenderDrawPoint(r,x,y);
      }

    // Modyfikujemy kolory
    for(i = 0; i < 2; i++)
      for(j = 0; j < 3; j++)
      {
        nc = ce[i][j] + cd[i][j];
        if((nc < 0) || (nc > 255)) cd[i][j] = -cd[i][j];
        ce[i][j] += cd[i][j];
      }

    // Uaktualniamy zawartość okna na ekranie
    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;
}

Wyjaśnijmy elementy programu:
ce[2][3] Składowe kolorów punktów narożnikowych przechowywane są w tablicy ce. Rozwiązanie takie przyjąłem dlatego, iż obliczenia dla składowych czerwonej, zielonej i niebieskiej są dokładnie takie same. Jednym z powodów stosowania tablic jest uproszczenie obliczeń. Zamiast wykonywać 3 razy to samo, wykorzystamy pętlę.

ce[0] określa składowe koloru pierwszego narożnika okna (lewy górny):

ce[0][0] – składowa czerwona
ce[0][1] – składowa zielona
ce[0][2] – składowa niebieska

Podobnie ce[1] określa składowe koloru drugiego narożnika (prawy dolny):

ce[1][0] – składowa czerwona
ce[1][1] – składowa zielona
ce[1][2] – składowa niebieska
cd[2][3] Ta tablica przechowuje przyrosty dla składowych koloru narożników:
cd[0][0], cd[1][0] – przyrost składowej czerwonej
cd[0][1], cd[1][1] – przyrost składowej zielonej
cd[0][2], cd[1][2] – przyrost składowej niebieskiej
c[3] Przechowuje składowe kolorów rysowanych punktów:
c[0] – składowa czerwona
c[1] – składowa zielona
c[2] – składowa niebieska
dc[3] Przechowuje różnicę składowych koloru narożników. Wartość ta występuje w podanym wyżej wzorze jako b - a:
dc[0] – różnica składowych czerwonych
dc[1] – różnica składowych zielonych
dc[2] – różnica składowych niebieskich
for(i = 0; i < 2; i++)
    for(j = 0; j < 3; j++)
    {
      ce[i][j] = rand() % 256;
      do cd[i][j] = -2 + rand() % 5; while(!cd[i][j]);
    }
Te dwie pętle losują wartości początkowe składowych kolorów narożników okna oraz ich przyrosty od -2 do 2. Przyrost 0 jest pomijany.
dd = W_W + W_H - 2; Odległość między narożnikami.
for(i = 0; i < 3; i++)
  dc[i] = ce[1][i] - ce[0][i];
Pętla oblicza różnice składowych kolorów narożników i umieszcza je w tablicy dc.
for(x = 0; x < W_W; x++)
      for(y = 0; y < W_H; y++) ...
Te dwie pętle przebiegają po wszystkich współrzędnych punktów w obszarze okna.
d = x + y; Odległość od pierwszego narożnika (lewy górny).
for(i = 0; i < 3; i++)
  c[i] = ce[0][i] + (dc[i] * d) / dd;
W tej pętli zostają wyliczone składowe koloru punktu na podstawie kolorów narożników oraz położenia punktu. Jest to dokładnie podany przez nas wzór dla poszczególnych składowych koloru:

Zwróć uwagę na kolejność mnożenia i dzielenia. Jest ważna! (dlaczego?)

SDL_SetRenderDrawColor(r,c[0],c[1],c[2],255);
SDL_RenderDrawPoint(r,x,y);
Gdy składowe koloru zostaną policzone, ustawiamy ten kolor i rysujemy punkt.


A teraz kilka zadań dla ciebie:

Dopisz do programu fragment, który narysuje na każdym kadrze ładną siatkę kropek w kolorze białym o oczku 8 x 8 pikseli:

Zmień kropki na kratkę o oczku 16 x 16 pikseli:

Zmień rysowanie punktów, tak aby powstawały w siatce 2 x 2 piksele:


Oprócz rysowania pojedynczych punktów SDL2 oferuje funkcję rysującą ciąg punktów, których współrzędne znajdują się w tablicy o elementach typu SDL_Pont:

struct SDL_Point
{
  int x;
  int y;
};

Funkcja posiada składnię:

SDL_RenderDrawPoints(r,p,n)

r – wskaźnik struktury SDL_Renderer z kontekstem graficznym,
p – tablica elementów typu SDL_Point, które przechowują współrzędne punktów do wyświetlenia.
n – liczba punktów w tablicy.

Punkty rysowane są w kolorze, który jest ustawiany przez funkcję SDL_SetRenderDrawColor().


Program kolejny będzie rysował przesuwające się tło, np. gwiazd. Punkty będą animowane w ten sposób, iż ich współrzędne są zwiększane o ten sam przyrost dla wszystkich punktów. Jednakże po zmianie sprawdzone zostanie, czy współrzędna nie wyszła poza krawędź okna. Jeśli tak, to będzie zmniejszana odpowiednio o szerokość okna dla współrzędnych poziomych lub o wysokość okna dla współrzędnych pionowych. W efekcie punkt pojawi się po drugiej stronie okienka i znów rozpocznie swój ruch.

Skopiuj do edytora poniższy program, skompiluj go i uruchom:

C++
// Punkty 4
//---------

#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
const int N = 1000;

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("Punkty", 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));

  // Ustalamy wartości początkowe

  // Tablica punktów
  SDL_Point P[N];

  for(int i = 0; i < N; i++)
  {
    P[i].x = rand() % W_W;
    P[i].y = rand() % W_H;
  }

  // Przyrosty
  int dx = 1 + rand() % 2;
  int dy = 1 + rand() % 2;

  // Animacja
  SDL_Event e;
  while(1)
  {
    // Kasujemy poprzednio narysowane punkty
    SDL_SetRenderDrawColor(r,0,0,0,255);
    SDL_RenderClear(r);

    // Rysujemy punkty
    SDL_SetRenderDrawColor(r,255,255,255,255);
    SDL_RenderDrawPoints(r,P,N);

    // Przesuwamy punkty
    for(int i = 0; i < N; i++)
    {
      P[i].x += dx;
      if(P[i].x >= W_W) P[i].x -= W_W;
      P[i].y += dy;
      if(P[i].y >= W_H) P[i].y -= W_H;
    }

    // Drobne opóźnienie
    SDL_Delay(30);

    // Uaktualniamy zawartość okna na ekranie
    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;
}

Elementy programu:

const int N = 1000; Liczba punktów, które będą przesuwane w oknie.
SDL_Point P[N]; Współrzędne punktów przechowuje tablica P. Każdy element tej tablicy jest strukturą SDL_Point, która zawiera dwa pola typu int: x i y.
for(int i = 0; i < N; i++)
{
  P[i].x = rand() % W_W;
  P[i].y = rand() % W_H;
}
losujemy współrzędne punktów w obszarze okna i zapamiętujemy je w tablicy P.
SDL_SetRenderDrawColor(r,0,0,0,255);
SDL_RenderClear(r);
Ustawiamy kolor czarny i wypełniamy nim cały obszar okna. W ten sposób pozbędziemy się z tekstury poprzednio narysowanych na niej punktów.
SDL_SetRenderDrawColor(r,255,255,255,255);
SDL_RenderDrawPoints(r,P,N);
Ustawiamy kolor biały i rysujemy hurtem wszystkie punkty w tablicy P.
for(int i = 0; i < N; i++)
{
  P[i].x += dx;
  if(P[i].x >= W_W) P[i].x -= W_W;
  P[i].y += dy;
  if(P[i].y >= W_H) P[i].y -= W_H;
}
Modyfikujemy współrzędne wszystkich punktów. Dodajemy do nich przesunięcia: dx do współrzędnych poziomych x i dy do współrzędnych pionowych y. Następnie sprawdzamy, czy dana współrzędna nie wyszła poza krawędź okna. Jeśli tak, to ją odpowiednio zmniejszamy: współrzędne x zmniejszamy o szerokość okna, a współrzędne y zmniejszamy o wysokość okna. W ten sposób punkt pojawi się z drugiej strony okna i nigdy nie wyjdzie poza jego obszar.

Kolejny program będzie obracał punkty wokół środka ekranu. Tego typu operację wykonuje się zwykle za pomocą rachunku macierzowego przez złożenie przekształceń. Jednak w tym miejscu kursu nie będę jeszcze wprowadzał macierzy przekształceń, ponieważ na to zagadnienie przeznaczę jeden cały rozdział. Obrót wykonamy przez wyprowadzenie wzoru na współrzędne punktu. Najpierw rozważmy prostszy przypadek: obrót punktu wokół osi układu współrzędnych o zadany kąt φ:

Jeśli znamy kat φ i współrzędne x,y punktu P, to naszym zadaniem będzie obliczenie współrzędnych x' i y' punktu P', który powstaje przez obrót punktu P o kąt φ wokół środka układu współrzędnych. Zwróć uwagę, że oba punkty P i P' znajdują się na obwodzie tego samego koła o promieniu r. Dla okręgu o środku w punkcie (0,0) układu współrzędnych mamy:

Z tego wzoru wyliczamy promień okręgu:

Z definicji funkcji trygonometrycznych mamy:

Teraz sięgamy do wzorów funkcji trygonometrycznych dla sumy kątów:

Dokonujemy podstawień:

Pozbywamy się promienia i otrzymujemy wzór ostateczny:

Zadanie rozwiązane. Co jednak zrobić, jeśli środek obrotu nie leży w środku układu współrzędnych? Tutaj z pomocą przychodzi przekształcenie, zwane translacją, które polega na przesunięciu punktu, tak aby jego środek obrotu znalazł się w środku układu współrzędnych. Po przesunięciu obracamy punkt i dokonujemy translacji odwrotnej, czyli przesuwamy środek obrotu punktu na poprzednie miejsce. Wygląda to tak (sx i sy to współrzędne środka obrotu):

Przenosimy współrzędne punktu, tak aby środek obrotu trafił do środka układu współrzędnych:

Współrzędne wykorzystujemy do obrotu:

Przywracamy oryginalne położenie środka obrotu:

Po wykonaniu podstawień, otrzymujemy ostateczne wzory:

Skopiuj do edytora poniższy program, skompiluj go i uruchom:

C++
// Punkty 5
//---------

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

using namespace std;

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

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

// Liczba obrotów cząstkowych na jeden pełny obrót
const int RN = 3000;

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("Punkty", 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));

  // Ustalamy wartości początkowe

  // Tablice punktów
  SDL_Point O[N],P[N];

  for(int i = 0; i < N; i++)
  {
    O[i].x = -W_W + rand() % (3 * W_W);
    O[i].y = -W_H + rand() % (3 * W_H);
  }

  // Środek obrotu
  const int sx = W_W / 2;
  const int sy = W_H / 2;

  // Kąt obrotu i jego przyrost
  double fi = 0;
  double df = 2 * M_PI / RN;

  // Animacja
  SDL_Event e;
  while(1)
  {
    // Kasujemy poprzednio narysowane punkty
    SDL_SetRenderDrawColor(r,0,0,0,255);
    SDL_RenderClear(r);

    // Obracamy punkty
    double cos_fi = cos(fi);
    double sin_fi = sin(fi);
    for(int i = 0; i < N; i++)
    {
       P[i].x = (O[i].x - sx) * cos_fi - (O[i].y - sy) * sin_fi + sx;
       P[i].y = (O[i].y - sy) * cos_fi + (O[i].x - sx) * sin_fi + sy;
    }

    // Rysujemy obrócone punkty
    SDL_SetRenderDrawColor(r,255,255,255,255);
    SDL_RenderDrawPoints(r,P,N);

    // Zwiększamy kąt obrotu
    fi += df;
    if(fi >= 2 * M_PI) fi = 0;

    // Drobne opóźnienie
    SDL_Delay(10);

    // Uaktualniamy zawartość okna na ekranie
    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;
}

Wyjaśnijmy elementy programu:

#include <cmath> Do programu musimy dołączyć plik z definicjami funkcji matematycznych sin() i cos().
const int RN = 3000; Stała określa liczbę obrotów cząstkowych na jeden obrót o kąt pełny. Im większa wartość, tym płynniejszy jest obrót punktów.
SDL_Point O[N],P[N]; Dane są przechowywane w dwóch tablicach. Tablica O zawiera podstawowe pozycje punktów i służy jako baza do obliczeń. Tablica P zawiera pozycje punktów po wykonaniu obrotu. Służy do wyświetlenia punktów.
for(int i = 0; i < N; i++)
{
  O[i].x = -W_W + rand() % (3 * W_W);
  O[i].y = -W_H + rand() % (3 * W_H);
}
Ta pętla losuje położenia punktów w obszarze wykraczającym poza okno (aby przy obrotach nie było widać pustych obszarów) i umieszcza współrzędne w tablicy O. Jeśli punkt jest poza obszarem graficznym okna, to nie będzie rysowany. Nie musimy się tym przejmować.
const int sx = W_W / 2;
const int sy = W_H / 2;
Definiujemy stałe określające położenie środka obrotu. Jest to oczywiście środek okna.
double fi = 0;
double df = 2 * M_PI / RN;
Obroty będą wykonywane o kąt fi, który będziemy stopniowo zwiększać, aż osiągnie kąt pełny. Wtedy zostanie wykonany pełen obrót. Zmienna df określa przyrost kąta przy każdym obrocie.
Stała M_PI to wartość liczby π. Zdefiniowana jest w pliku nagłówkowym cmath.
SDL_SetRenderDrawColor(r,0,0,0,255);
SDL_RenderClear(r);
Czyścimy okno z poprzednio narysowanych punktów. Ustawiamy kolor czarny i wypełniamy nim całą powierzchnię graficzną okna.
double cos_fi = cos(fi);
double sin_fi = sin(fi);
We wzorze na współrzędne obróconego punktu występują funkcje sin() i cos() dla kąta fi. Kąt ten zmienia się dopiero po przeliczeniu współrzędnych wszystkich punktów. Aby nie tracić czasu na obliczanie funkcji trygonometrycznych dla każdego punktu, zapamiętujemy ich wartości w zmiennych sin_fi i cos_fi.
for(int i = 0; i < N; i++)
 {
   P[i].x = (O[i].x - sx) * cos_fi - (O[i].y - sy) * sin_fi + sx;
   P[i].y = (O[i].y - sy) * cos_fi + (O[i].x - sx) * sin_fi + sy;
}
Wyliczamy współrzędne punktów po obrocie i zapisujemy je do tablicy P. Dane pobieramy we wzorze z tablicy O, która przechowuje położenia oryginalne punktów.  Takie rozwiązanie eliminuje błędy obliczeniowe.
SDL_SetRenderDrawColor(r,255,255,255,255);
SDL_RenderDrawPoints(r,P,N);
Gdy wszystkie punkty zostały obliczone, ustawiamy kolor biały i hurtem rysujemy zawartość całej tablicy P.
fi += df;
if(fi >= 2 * M_PI) fi = 0;
Zwiększamy kąt obrotu o wyliczony przyrost. W ten sposób następny obrót będzie o większy kąt. Jednak, gdy kat osiągnie wartość kata pełnego, zerujemy go. W sumie nie jest to konieczne, ale w ten sposób zmniejszamy ewentualne błędy zaokrągleń kąta.

Reszta programu jest już standardowa.


Jako ćwiczenie spróbuj zmodyfikować ten program, tak aby środek obrotu nie był stały, lecz poruszał się w oknie. Wykorzystaj poprzednie programy, gdzie odbijane były punkty od krawędzi okna.

Na początek:  podrozdziału   strony 

Odczyt piksela

Odczyt piksela polega na tym, iż otrzymujesz wartości składowych koloru, który ma piksel na pozycji x,y obszaru okna. Jeśli zawartość okna jest reprezentowana strukturą SDL_Surface, to mikroprocesor posiada bezpośredni dostęp do wszystkich pikseli i operację tę można wykonać podobnie do operacji stawiania piksela, którą przedstawiliśmy na początku tego rozdziału.

Poniższy program wypełnia okno pikselami o różnych kolorach, a następnie dokonuje cyklicznej rotacji ich składowych kolorów: rg; g  → b; b  → r. Wymaga to odczytania składowych koloru poszczególnych pikseli okna.

W CodeBlocks utwórz nowy projekt sdl2, skopiuj do edytora poniższy program, skompiluj i uruchom go:

C++
// Odczyt punktów 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
const int N = 5000;

// Funkcja stawia piksel o zadanym kolorze na pozycji x,y obrazu
//--------------------------------------------------------------
void Plotxyc(SDL_Surface * s, int x, int y, Uint8 r,  Uint8 g,  Uint8 b,  Uint8 a)
{
  Uint32 color, * addr;

  // Obliczamy adres piksela
  addr = (Uint32 *)s->pixels + x + y * s->w;

  // Obliczamy kod piksela
  color  = (r << s->format->Rshift) & (s->format->Rmask);
  color |= (g << s->format->Gshift) & (s->format->Gmask);
  color |= (b << s->format->Bshift) & (s->format->Bmask);
  color |= (a << s->format->Ashift) & (s->format->Amask);

  // Umieszczamy piksel na obrazie
  * addr = color;
}

// Funkcja odczytuje kolory piksela na pozycji x,y
//------------------------------------------------
void ReadPixelColor(SDL_Surface * s, int x, int y, Uint8 & r,  Uint8 & g,  Uint8 & b,  Uint8 & a)
{
  Uint32 color, * addr;

  // Obliczamy adres piksela
  addr = (Uint32 *)s->pixels + x + y * s->w;

  // Odczytujemy kolor piksela
  color = * addr;

  // Wydobywamy składowe z kodu piksela
  r = (color & s->format->Rmask) >> s->format->Rshift;
  g = (color & s->format->Gmask) >> s->format->Gshift;
  b = (color & s->format->Bmask) >> s->format->Bshift;
  a = (color & s->format->Amask) >> s->format->Ashift;
}

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("Punkty", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, W_W, W_H, 0);
  if(!w)
  {
    cout << "SDL_CreateWindow Error: " << SDL_GetError() << endl;
    SDL_Quit();
    return 2;
  }

    // Powierzchnia graficzna okna
  SDL_Surface * s = SDL_GetWindowSurface(w);

  // Sprawdzamy, czy piksele są 32-bitowe. Jeśli nie, kończymy
  if(s->format->BitsPerPixel != 32)
  {
    cout << "Pixels not 32 bits in size" << endl;
    SDL_DestroyWindow(w);
    SDL_Quit();
    return 2;
  }

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

  // Wypełniamy okno pikselami o róźnych kolorach
  SDL_LockSurface(s);
  for(int i = 0; i < N; i++) Plotxyc(s,rand()%W_W,rand()%W_H,rand(),rand(),rand(),255);
  SDL_UnlockSurface(s);

  // Animacja
  SDL_Event e;
  while(1)
  {
    // Wykonujemy rotację składowych kolorów wszystkich pikseli na powierzchni graficznej
    Uint8 r,g,b,a;

    SDL_LockSurface(s);
    for(int x = 0; x < W_W; x++)
      for(int y = 0; y < W_H; y++)
      {
        ReadPixelColor(s,x,y,r,g,b,a);
        Plotxyc(s,x,y,b,r,g,a);
      }

    SDL_UnlockSurface(s);
    SDL_UpdateWindowSurface(w);
    SDL_Delay(250);

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

  // Usuwamy okno
  SDL_DestroyWindow(w);

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

  return 0;
}

Opiszmy nowe elementy programu:

void Plotxyc(...) Funkcja rysuje piksel na powierzchni SDL_Surface. Opisaliśmy ją w pierwszym programie tego rozdziału.
void ReadPixelColor(...) Funkcja odczytuje składowe koloru piksela na zadanej pozycji powierzchni SDL_Surface okna. Poniżej jej opis:
Uint32 color, * addr; Zmienna color będzie zawierała kod koloru piksela, zmienna addr będzie zawierała jego adres w pamięci RAM.
addr = (Uint32 *)s->pixels + x + y * s->w; Obliczamy adres piksela w RAM.
color = * addr; Pobieramy kod koloru piksela
r = (color & s->format->Rmask) >> s->format->Rshift;
g = (color & s->format->Gmask) >> s->format->Gshift;
b = (color & s->format->Bmask) >> s->format->Bshift;
a = (color & s->format->Amask) >> s->format->Ashift;
Na podstawie informacji o formacie piksela w strukturze SDL_Surface z kodu koloru wydobywamy kolejne składowe.
SDL_LockSurface(s);
for(int i = 0; i < N; i++) Plotxyc(s,rand()%W_W,rand()%W_H,rand(),rand(),rand(),255);
SDL_UnlockSurface(s);
Rysujemy na powierzchni piksele w różnych kolorach i na różnych pozycjach.
Uint8 r,g,b,a; Te zmienne przechowują składowe koloru piksela.
SDL_LockSurface(s);
for(int x = 0; x < W_W; x++)
  for(int y = 0; y < W_H; y++)
  {
    ReadPixelColor(s,x,y,r,g,b,a);
    Plotxyc(s,x,y,b,r,g,a);
  }
Przeglądamy całą powierzchnię okna punkt po punkcie i wykonujemy rotację składowych koloru
SDL_UnlockSurface(s);
SDL_UpdateWindowSurface(w);
SDL_Delay(250);
Uaktualniamy obraz okna z powierzchni graficznej i czekamy 1/4 sekundy.

Reszta programu jest standardowa


Powierzchnia SDL_Surface znajduje się w pamięci RAM i akcelerator grafiki nie ma do niej bezpośredniego dostępu. W bibliotece SDL2 brak funkcji odczytującej z tekstury w pamięci VRAM kolor pojedynczego piksela, co nie oznacza, iż operacji tej nie da się wykonać. Można to zrobić na kilka sposobów, jednak przesyłanie danych pomiędzy pamięciami RAM i VRAM nie jest tak efektywne, jak działanie tylko na pamięci VRAM przez układy logiczne akceleratora. Dlatego, jeśli program chce przetworzyć dużą liczbę pikseli, dobrym rozwiązaniem jest tymczasowe skopiowanie pikseli całej tekstury okna do pamięci RAM, wykonanie modyfikacji, a następnie uaktualnienie hurtem pikseli tekstury w pamięci VRAM karty graficznej. Takimi operacjami zajmiemy się w dalszej części kursu.


Program poniżej jest wersją poprzedniego programu, jednak tutaj nie tworzymy powierzchni SDL_Surface w pamięci RAM, lecz wykorzystujemy kontekst graficzny i operacje przesłania z pamięci VRAM do RAM. Skopiuj program do edytora, skompiluj i uruchom go:

C++
// Odczyt punktów 2
//-----------------

#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
const int N = 5000;

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("Punkty", 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));

  // Wypełniamy okno pikselami o róźnych kolorach
  for(int i = 0; i < N; i++)
  {
    SDL_SetRenderDrawColor(r,rand(),rand(),rand(),255);
    SDL_RenderDrawPoint(r,rand() % W_W,rand() % W_H);
  }

  // Tablica kolorów punktów
  SDL_Color pixels[W_H][W_W];

  SDL_Rect rect;
  rect.x = rect.y = 0;
  rect.w = W_W;
  rect.h = W_H;

  // Animacja
  SDL_Event e;
  while(1)
  {
    // Czytamy piksele z tekstury do tablicy
    SDL_RenderReadPixels(r,&rect,SDL_PIXELFORMAT_ABGR8888,pixels,W_W * 4);

    for(int x = 0; x < W_W; x++)
      for(int y = 0; y < W_H; y++)
      {
        SDL_SetRenderDrawColor(r,pixels[y][x].b,pixels[y][x].r,pixels[y][x].g,255);
        SDL_RenderDrawPoint(r,x,y);
      }

    SDL_RenderPresent(r);

    SDL_Delay(250);

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

Elementy programu:

for(int i = 0; i < N; i++)
{
  SDL_SetRenderDrawColor(r,rand(),rand(),rand(),255);
  SDL_RenderDrawPoint(r,rand() % W_W,rand() % W_H);
}
Rysujemy na powierzchni okna N punktów o różnych kolorach i na różnych pozycjach
SDL_Color pixels[W_H][W_W]; Tablica pixels jest dwuwymiarową tablicą struktur SDL_Color, tzn. element pixels[y][x] będzie strukturą SDL_Color dla piksela na pozycji x,y w oknie. Dlaczego współrzędne są odwrócone? Jest to konsekwencja zapisu obrazu w pamięci. Obraz zapisywany jest kolejnymi liniami poziomymi. Linie odpowiadają współrzędnym y. Zawartość linii to piksele, które odpowiadają współrzędnym x. Stąd element pixels[y][x] odnosi się do piksela leżącego na pozycji x w linii o numerze y.

Struktura SDL_Color ma cztery pola typu Uint8:

struct SDL_Color
{
    Uint8 r,g,b,a;
};
SDL_Rect rect;
rect.x = rect.y = 0;
rect.w = W_W;
rect.h = W_H;
Ta struktura zawiera definicję prostokąta obejmującego całą powierzchnię okna. Potrzebujemy jej do kopiowania pikseli z tekstury w VRAM do tablicy w RAM.
SDL_RenderReadPixels(r,&rect,SDL_PIXELFORMAT_ABGR8888,pixels,W_W * 4); Funkcja SDL_RenderReadPixels() odczytuje z tekstury okna prostokątny obszar pikseli i umieszcza go we wskazanym miejscu w pamięci RAM. Parametry są następujące:
xxxSDL_RenderReadPixels(r,rt,f,px,p)
r – wskaźnik struktury SDL_Renderer, która opisuje kontekst graficzny.
rt – wskaźnik struktury SDL_Rect, która definiuje obszar do odczytania w oknie.
f – docelowy format pikseli. Tutaj użyłem formatu SDL_PIXELFORMAT_ABGR8888,
      który oznacza składowe 8-bitowe w kolejności od najstarszego bajtu: A,B,G i R.
      Kolejność ta odpowiada kolejności pól składowych koloru w strukturze
      SDL_Color (dla komputerów little endian z procesorami Intel).
px – wskaźnik obszaru, w którym zostaną umieszczone kody kolorów pikseli.
p – liczba bajtów na jeden wiersz pikseli w obszarze docelowym.

Funkcja kopiuje piksele z tekstury do tablicy pixels, z której będziemy mogli wygodnie odczytywać ich kolory.

for(int x = 0; x < W_W; x++)
  for(int y = 0; y < W_H; y++)
  {
    SDL_SetRenderDrawColor(r,pixels[y][x].b,pixels[y][x].r,pixels[y][x].g,255);
    SDL_RenderDrawPoint(r,x,y);
  }
Przeglądamy tablicę pixels i wykonujemy na podstawie jej zawartości rotację składowych koloru poszczególnych pikseli na teksturze.

Reszta programu jest standardowa.

Na początek:  podrozdziału   strony 

Podsumowanie

SDL_RenderClear(r) – wypełnia całą powierzchnię graficzną okna bieżącym kolorem.

r – wskaźnik struktury SDL_Renderer

SDL_RenderDrawPoint(r,x,y) – rysuje punkt w bieżącym kolorze.

r – wskaźnik struktury SDL_Renderer
x,y – współrzędne punktu

SDL_RenderDrawPoints(r,p,n) – rysuje zadaną liczbę punktów z tablicy

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

SDL_RenderReadPixels(r,rt,f,px,p)

r – wskaźnik struktury SDL_Renderer
rt – wskaźnik struktury SDL_Rect, która definiuje obszar do odczytania w oknie.
f – docelowy format pikseli.
px – wskaźnik obszaru, w którym zostaną umieszczone kody kolorów pikseli.
p – liczba bajtów na jeden wiersz pikseli w obszarze docelowym

Na początek:  podrozdziału   strony 

Zespół Przedmiotowy
Chemii-Fizyki-Informatyki

w I Liceum Ogólnokształcącym
im. Kazimierza Brodzińskiego
w Tarnowie
ul. Piłsudskiego 4
©2023 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.