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       Poprzedni       Następny  

©2019 mgr Jerzy Wałaszek
I LO w Tarnowie

logo

Autor artykułu: mgr Jerzy Wałaszek

 

 

SDL2

Grafika rastrowa

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:
Nieco historii
Tworzenie obrazu

 

Nieco historii

W tym rozdziale postaram się przedstawić ci sposoby tworzenia grafiki na współczesnych komputerach. Jednak najpierw przyda ci się trochę historii, co pozwoli ci zrozumieć niektóre problemy, które występują w grafice komputerowej nawet dzisiaj.

Tryb tekstowy

Współczesny młody człowiek nie zdaje sobie sprawy, jak bardzo rozwinął się sprzęt komputerowy w ciągu ostatnich 30 lat. Ma wypasiony telefon komórkowy, w domu gra na komputerze w gry prezentujące obraz w jakości filmowej, na telewizorze ogląda programy w jakości HD (ang. high definition – wysoka jakość) nadawane cyfrowo, itd. Nie ma w tym nic złego. Rozwój nie jest zły (o ile nie dotyczy techniki wojennej). Bez niego wciąż biegalibyśmy z maczugami wokół jaskini (co również nie było takie całkiem złe):

Mój pierwszy komputer (rok 1983, ZX-81) nie posiadał wcale grafiki.

Potrafił jedynie wyświetlać literki, a i to nie wszystkie. Na przykład wykres funkcji wyglądał na ekranie ZX-81 tak:

Punkty i linie były tworzone ze znaków o specjalnych kształtach (tzw. semigrafika znakowa). Związane to było z kilkoma czynnikami:

 

Dobra grafika wymaga dużo pamięci na przechowanie obrazu, w początkowych latach komputeryzacji pamięci były bardzo drogie i komputery miały jej po prostu mało. Na przykład typowy tryb tekstowy potrzebuje niewiele więcej pamięci niż na ekranie może zmieścić się znaków, ponieważ w pamięci obrazu przechowywane były tylko kody liter. Kształt liter przechowywany był gdzie indziej (w tzw. matrycach znakowych). ZX-81 mógł wyświetlać obraz złożony z 24 wierszy po 32 znaki w wierszu, co daje 24 x 32 = 768 bajtów. Standardowy model komputera posiadał tylko 1KB (słownie 1024 bajty!) pamięci RAM, więc nawet w tym trybie tekstowym użytkownikowi pozostawało tylko 200...250 bajtów na program i dane. Parę linijek kodu i już pamięć się kończyła. Horror! Dlatego, aby sensownie pracować z tym komputerem, należało dokupić rozszerzenie pamięci (na zdjęciu wyżej widoczne jest takie rozszerzenie 16KB – czarne pudełko z tyłu komputera z napisem ZX 16KB RAM). A takie rozszerzenie kosztowało drugie tyle co sam komputer.

 

Dobra grafika wymaga szybkiego procesora do przetwarzania danych. Pierwsze procesory nie należały do demonów szybkości, zatem obsługa obrazu o dużej liczbie kolorów i o dużej rozdzielczości była czasochłonna. Pamiętam, jak w latach 80-tych byłem na giełdzie elektronicznej w Krakowie. Widziałem tam "nowoczesne" komputery wyświetlające obrazy o jakości fotograficznej. Lecz zmiana takiego obrazu trwała kilka sekund i widać było, jak obrazek był rysowany linia po linii.

 

Dobra grafika wymaga odpowiednio zaawansowanej elektroniki. W pierwszych komputerach stosowano tanie rozwiązania, aby zwiększyć sprzedaż.

 

Powodów można by znaleźć więcej, ale to nie jest nasz problem.

Tryb tekstowy wciąż jest obecny na współczesnych komputerach, niekiedy w czasie startu system pracuje właśnie w trybie tekstowym. Nowsze komputery stosują już wyłącznie tryb graficzny, a Windows 10 blokuje możliwość przełączenia karty w ten tryb. Nie wróży mu to dobrze i pewnie niedługo tryb tekstowy zniknie z kart graficznych.

Zasada działania trybu tekstowego jest w miarę prosta:

Obraz na ekranie monitora składa się z linii (tutaj przesadnie pokazanych):

Linie te są wyświetlane kolejno z góry na dół, aż zostanie wyświetlona cała ramka obrazu (ang. frame), po czym rysowana jest kolejna. Na sekundę tych ramek jest kilkadziesiąt (parametr nosi nazwę fps = frames per second, czyli ramki na sekundę). Nasze oczy nie widzą tego procesu, ponieważ odbywa się zbyt szybko.

Dane do wyświetlenia na ekranie przechowywane są w pamięci RAM (ang. Random Access Memory, czyli pamięć o dostępie swobodnym, traci zawartość po wyłączeniu zasilania) w tzw. buforze ekranu (ang. display buffer). Układ graficzny odczytuje kody znaków z bufora ekranu, a następnie na podstawie matryc znakowych (przechowują kształt znaków w postaci punktów zwanych pikselami) i numerów linii generuje treść kolejnych linii obrazowych:

Zwróć uwagę, iż obraz każdego znaku tworzony jest w siatce n x m punktów. Punkty te nazywamy pikselami. Tutaj mogą być jasne lub ciemne. Skoro tak, to piksele w matrycach da się przedstawić za pomocą bitów. Zatem każda taka matryca, to kilka kolejnych bajtów w pamięci:

Matryce mogą być przechowywane w pamięci RAM komputera (wtedy komputer może zmienić sobie kształt literek lub zastąpić nieużywane znaki innymi, które są mu akurat potrzebne – tak początkowo odbywało się spolszczanie programów) lub w pamięci ROM (ang. Read Only Memory, czyli pamięć tylko do odczytu, pamięć stała, nie traci zawartości po wyłączeniu zasilania) układu graficznego (wtedy kształt literek jest stały i użytkownik nie może go sobie zmieniać).

Zaletą trybu tekstowego jest niskie zapotrzebowanie na pamięć i duża szybkość działania programów, ponieważ obraz zawiera niewiele danych. W trybach znakowych wciąż działają niektóre wyświetlacze stosowane w mikrokontrolerach:

Elektronicy chętnie je stosują z uwagi na proste sterowanie. Jak widzisz, są obszary zastosowań informatyki, gdzie stare rozwiązania wciąż jeszcze dobrze się mają.

Ludzie się czasami dobrze bawią w trybie tekstowym: https://s3.eu-central-1.amazonaws.com/ascii-art-history/ascii-the_incredibles1.htm.

Tryb tekstowy w kolorze

Po okresie wyświetlaczy monochromatycznych ludzie pracujący na komputerach zapragnęli coś więcej niż tylko jednokolorowe literki. Dodano zatem kolor do trybu tekstowego. Rozwiązań było kilka, ale skupmy się na tym, co działo się w komputerach PC, ponieważ współczesne karty wciąż to mogą zawierać.

Kolor zrealizowano w ten sposób, iż w obszarze ekranu oprócz kodów znaków umieszczano dodatkowe informacje. Każda pozycja znakowa (obszar wyświetlający literę) składała się teraz z dwóch bajtów: bajtu z kodem znaku oraz tzw. bajtu atrybutu, w którym zawarty był kolor znaku i kolor jego tła. Spójrz jeszcze raz na matrycę literki A:

Matryca definiuje jedynie punkty białe i czarne. Kolory te można zastąpić innymi przez określenie kolorów wynikowych dla punktów białych i czarnych matrycy:

Jak widzisz, już jest ciekawiej.

Wyświetlanie kolorowych literek działa podobnie do monochromatycznego tryby znakowego.

Układ graficzny pobiera z bufora ekranu kolejno: bajt kodu znaku i bajt atrybutu. Na podstawie kodu znaku i numeru linii obrazu pobiera z pamięci matryc znaków bajt matrycy znakowej. Kolor biały i czarny zastępuje kolorami zdefiniowanymi w atrybucie koloru i wynik przesyła na wyjście do monitora. W efekcie na ekranie monitora powstaje kolorowy obraz:

Aby zrozumieć kodowanie kolorów, musisz wiedzieć jak monitory wyświetlają kolorowy obraz. Zdobądź dobrą lupę i spójrz z bliska na ekran swojego komputera (może też być smartfon). Zauważysz, że obraz utworzony jest z małych punktów w kolorach czerwonym, zielonym i niebieskim:

Układ tych punktów może być różny, niemniej zawsze występują w trzech podanych kolorach. Trójka kolorowych punktów nosi nazwę triady RGB (ang. R - red, czerwony; G - green, zielony; B - blue, niebieski). Triada tworzy jeden punkt obrazu, który nazywamy pikselem (ang. pixel = picture element, element obrazu). Regulując natężenie świecenia kolorów triady uzyskuje się różne kolory piksela:

R G B RGB
       
       
       
       
       
       
       
       

Atrybut koloru składa się z 8 bitów. Bity te są podzielone na dwie grupy po 4 bity. Dolna grupa określa kolor znaku. Górna grupa określa kolor tła:

kolor tła kolor liter
I R G B I R G B
b7 b6 b5 b4 b3 b2 b1 b0

Jeśli są ustawione na 1, poszczególne bity atrybutu koloru posiadają następujące znaczenie:

I - rozjaśnienie koloru, tzw. bit intensywności
R - składowa czerwona (ang. RED)
G - składowa zielona (ang. GREEN)
B - składowa niebieska (ang. BLUE)

Bit intensywności rozjaśnia kolory podstawowe, dzięki czemu uzyskujemy jaśniejszy kolor wynikowy. W poniższej tabelce zebrano wszystkie kolory wraz z ich kodami dwójkowymi, szesnastkowymi i dziesiętnymi:

binarnie szesnastkowo dziesiętnie kolor
0000 0 0  
0001 1 1  
0010 2 2  
0011 3 3  
0100 4 4  
0101 5 5  
0110 6 6  
0111 7 7  
1000 8 8  
1001 9 9  
1010 A 10  
1011 B 11  
1100 C 12  
1101 D 13  
1110 E 14  
1111 F 15  

Na przykład chcemy otrzymać wartość atrybutu dla znaku intensywnie żółtego na brązowym tle: łączymy kod koloru tła z kodem koloru znaku: 0x4 i 0xe = 0x4e.

Tryb tekstowy w kolorze dostępny jest w oknie konsoli Windows.


Poniższy program demonstruje sposób uzyskania kolorów. Działa tylko w Windows.

Utwórz w CodeBlocks projekt aplikacji konsoli, do edytora przekopiuj poniższy program, skompiluj go i uruchom:

 

//            B O M B O W I E C
//--------------------------------------------
// (C)2018 mgr Jerzy Wałaszek  I LO w Tarnowie

#include <conio.h>
#include <windows.h>
#include <strings.h>
#include <iostream>
#include <cstdlib>
#include <ctime>

using namespace std;

//----------------------------------------------------
// Deklaracja klasy gry
//----------------------------------------------------

class TMiasto
{
  public:
    int  domy[81];
    void Inicjuj();
    int  Koniec(int x, int y);
};

class TBomba
{
    int  oby;
  public:
    int  bx,by;
    void Inicjuj();
    void ZrzutBomb(int x, int y);
    void LotBomby(int * y);
};

class TSamolot
{
    int ox,oy;
  public:
    int  sx,sy;
    void Inicjuj();
    void LotBombowca();
};

class TGra
{
    TMiasto  miasto;
    TBomba   bomba;
    TSamolot samolot;
    int  a;
    void StronaTytulowa();
  public:
    void Inicjuj();
    void Uruchom();
    int  Koniec();
};

//----------------------------------------------------
// PROCEDURY I FUNKCJE POMOCNICZE
//----------------------------------------------------

const int BLACK        = 0;
const int BLUE         = 1;
const int GREEN        = 2;
const int CYAN         = 3;
const int RED          = 4;
const int MAGENTA      = 5;
const int BROWN        = 6;
const int LIGHTGRAY    = 7;
const int DARKGRAY     = 8;
const int LIGHTBLUE    = 9;
const int LIGHTGREEN   = 10;
const int LIGHTCYAN    = 11;
const int LIGHTRED     = 12;
const int LIGHTMAGENTA = 13;
const int YELLOW       = 14;
const int WHITE        = 15;

static int __BACKGROUND = BLACK;
static int __FOREGROUND = LIGHTGRAY;

// Procedura ustawia pozycję wydruku w oknie konsoli
//----------------------------------------------------
void gotoxy(int x, int y)
{
  COORD c;

  c.X = x - 1;
  c.Y = y - 1;
  SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), c);
}

// Procedura ustawia kolor tła wydruku
//----------------------------------------------------
void textbackground(int color)
{
  __BACKGROUND = color;
  SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),
    __FOREGROUND + (color << 4));
}

// Procedura ustawia kolor tekstu
//----------------------------------------------------
void textcolor(int color)
{
  __FOREGROUND = color;
  SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),
    color + (__BACKGROUND << 4));
}

// Procedura ustawia atrybuty koloru tekstu i tła
//----------------------------------------------------
void textattr(int _attr)
{
  SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), _attr);
}

// Funkcja zwraca aktualną pozycję x kursora
//----------------------------------------------------
int wherex()
{
  CONSOLE_SCREEN_BUFFER_INFO info;

  GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info);
  return info.dwCursorPosition.X + 1;
}

// Funkcja zwraca aktualną pozycję y kursora
//----------------------------------------------------
int wherey()
{
  CONSOLE_SCREEN_BUFFER_INFO info;

  GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info);
  return info.dwCursorPosition.Y + 1;
}

// Procedura czyści zawartość okna konsoli
//----------------------------------------------------
void clrscr()
{
  DWORD written;

  FillConsoleOutputAttribute(GetStdHandle (STD_OUTPUT_HANDLE),
    __FOREGROUND + (__BACKGROUND << 4), 2000, (COORD){0, 0}, &written);
  FillConsoleOutputCharacter(GetStdHandle(STD_OUTPUT_HANDLE), ' ',
    2000, (COORD){0, 0}, &written);
  gotoxy(1, 1);
}

// Procedura ukrywa kursor okienka konsoli
//------------------------------------------------------
void CursorOff()
{
  CONSOLE_CURSOR_INFO Info;

  GetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &Info);
  Info.bVisible = 0;
  SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &Info);
};

// Procedura przywraca kursor okienka konsoli
//------------------------------------------------------
void CursorOn()
{
  CONSOLE_CURSOR_INFO Info;

  GetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &Info);
  Info.bVisible = -1;
  SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &Info);
};

// Funkcja PL konwertuje tekst ze standardu Windows 1250
// na standard konsoli znakowej Latin II
//------------------------------------------------------
string PL(string s)
{
  unsigned int i;
  char c;

  for(i = 0; i < s.length(); i++)
  {
    switch(s[i])
    {
      case 'ą' : c = char(165); break;
      case 'ć' : c = char(134); break;
      case 'ę' : c = char(169); break;
      case 'ł' : c = char(136); break;
      case 'ń' : c = char(228); break;
      case 'ó' : c = char(162); break;
      case 'ś' : c = char(152); break;
      case 'ż' : c = char(190); break;
      case 'ź' : c = char(171); break;
      case 'Ą' : c = char(164); break;
      case 'Ć' : c = char(143); break;
      case 'Ę' : c = char(168); break;
      case 'Ł' : c = char(157); break;
      case 'Ń' : c = char(227); break;
      case 'Ó' : c = char(224); break;
      case 'Ś' : c = char(151); break;
      case 'Ż' : c = char(189); break;
      case 'Ź' : c = char(141); break;
      default:   c = s[i];
    };
    s[i] = c;
  };
  return(s);
}

// Procedura centruje w bieżącym wierszu tekst podany jako parametr
//-----------------------------------------------------------------
void Centruj(string t)
{
  gotoxy(1 + (80 - t.length()) / 2, wherey()); cout << t << endl;
}

// Procedura rysuje pojedynczą ramkę ze znaków
// tabelek. Parametry określają współrzędne
// lewego górnego i prawego dolnego narożnika
//----------------------------------------------------
void Ramka(int xp,int yp,int xk,int yk)
{
  int i;

  gotoxy(xp,yp); putch(char(218)); gotoxy(xp,yk); putch(char(192));
  gotoxy(xk,yp); putch(char(191)); gotoxy(xk,yk); putch(char(217));
  for(i = xp + 1; i <= xk - 1; i++)
  {
    gotoxy(i,yp); putch(char(196)); gotoxy(i,yk); putch(char(196));
  };
  for(i = yp + 1; i <= yk - 1; i++)
  {
    gotoxy(xp,i); putch(char(179)); gotoxy(xk,i); putch(char(179));
  };
}

// Procedura wprowadza opóźnienie o zadaną ilość milisekund
//---------------------------------------------------------
void Delay(unsigned int d)
{
  long start;

  start = GetTickCount();
  while((GetTickCount() - start) < d);
}

//----------------------------------------------------
// Definicje metod klas
//----------------------------------------------------

void TMiasto::Inicjuj()
{
  int i,j;

  textbackground(0xb); clrscr(); // tło nieba
  textattr(0x1e);           //granatowe domy
  for(i = 1; i <= 80; i++)
  {
    if((i >= 6) && (i <= 75))
    {
      domy[i] = 1 + rand() % 10;
      for(j = 1; j <= domy[i]; j++)
      {
        gotoxy(i,26-j); cout << "-";
      };
    }
    else domy[i] = 0;
  };
}

int TMiasto::Koniec(int x, int y)
{
  int i,t;

  if(y == domy[x])
  {
    textattr(0xce); gotoxy(x,26-y); cout << "#";
    textattr(0xb4); cout << " KRACH!";
    textattr(0x1f); gotoxy(1,6); Centruj(PL(" GRA SKOŃCZONA "));
    return(true);
  }
  else
  {
    t = 0;
    for(i = 6; i <= 75; t = t + domy[i++]);
    if(t == 0)
    {
      textattr(0x1e); gotoxy(1,6); Centruj(" GRATULACJE! ");
      return(true);
    };
  };
  return(false);
}

//------------------------------------------------------

void TBomba::Inicjuj()
{
  bx = 1; by = oby = 0;
}

void TBomba::ZrzutBomb(int x, int y)
{
  char c;

  if(kbhit())
  {
    while(!(c = getch())) ;
    if((c == ' ') && !by)
    {
      bx = x; by = y; oby = by - 1;
    };
  };
}

void TBomba::LotBomby(int * y)
{
  if(by)
  {
    textattr(0xb0); gotoxy(bx,26-oby); cout << " ";
    oby = --by;
    if(*y > by)
    {
      *y = by; textattr(0xce);
    };
    if(by)
    {
      gotoxy(bx,26-by); cout << "*";
    };
  };
}

//------------------------------------------------------

void TSamolot::Inicjuj()
{
  sx = ox = 1; sy = oy = 25;
}

void TSamolot::LotBombowca()
{
  if(++sx > 80)
  {
    sx = 1; --sy;
  };
  gotoxy(ox,26-oy); textattr(0xb0); cout << " ";
  gotoxy(sx,26-sy); cout << ">";
  ox = sx; oy = sy;
}

//------------------------------------------------------

void TGra::StronaTytulowa()
{
  textbackground(0); clrscr(); gotoxy(1,4); textattr(0x5e);
  Centruj(PL("                                  "));
  Centruj(PL("         B O M B O W I E C        "));
  Centruj(PL("       =====================      "));
  Centruj(PL("                                  "));
  textattr(0x1f);
  Centruj(PL("                                  "));
  Centruj(PL("    (C)2018 mgr Jerzy Wałaszek    "));
  Centruj(PL("                                  "));
  textattr(0xf0);
  Centruj(PL("                                  "));
  Centruj(PL(" Przygotuj się na bombardowanie..."));
  Centruj(PL("                                  "));
  textattr(0xf4);
  Centruj(PL("        Gdy będziesz gotowy,      "));
  Centruj(PL("     naciśnij dowolny klawisz     "));
  Centruj(PL("                                  "));
  while(getch() == 0); // Oczekiwanie na dowolny klawisz
  textattr(0x07); clrscr();
}

void TGra::Inicjuj()
{
  StronaTytulowa();
  miasto.Inicjuj();
  samolot.Inicjuj();
  bomba.Inicjuj();
}

void TGra::Uruchom()
{
  do
  {
    samolot.LotBombowca();
    bomba.ZrzutBomb(samolot.sx,samolot.sy);
    bomba.LotBomby(&miasto.domy[bomba.bx]);
    Delay(100);
  } while(!miasto.Koniec(samolot.sx,samolot.sy));
}

int TGra::Koniec()
{
  gotoxy(1,2); textattr(0x4e);
  Centruj("                                       ");
  Centruj(" Jeszcze raz ? [T] = Tak, [Inny] = Nie ");
  Centruj("                                       ");
  return(toupper(getch()) != 'T');
}

int main()
{
  TGra gra;

  srand((unsigned)time(NULL)); CursorOff();

  do
  {
    gra.Inicjuj();
    gra.Uruchom();
  } while(!gra.Koniec());
  CursorOn(); textbackground(0); textcolor(7); clrscr(); return(0);
}

 

Karta graficzna Hercules

W trybach tekstowych jest ograniczenie tylko do zdefiniowanych znaków, nie można wyświetlać dowolnych obrazów. Dlatego od połowy lat 80-tych zaczęły pojawiać się komputery wyposażone w karty graficzne, które oprócz tekstu potrafiły również wyświetlać obrazy z pikseli. Obraz zbudowany z pikseli, czyli punktów nazywamy obrazem rastrowym (ang. raster – siatka prostokątnych punktów). Cechuje go tzw. rozdzielczość, czyli liczba wierszy i kolumn rastra oraz  liczba kolorów możliwych jednocześnie do wyświetlenia na ekranie, tzw. głębia koloru.

W naszym kraju dużą popularnością cieszyła się kiedyś karta graficzna Hercules. Umożliwiała ona pracę w monochromatycznym trybie tekstowym (25 wierszy po 80 znaków) oraz w trybie graficznym o rozdzielczości 720 punktów w poziomie na 348 punktów w pionie. Na owe czasy był to potężny skok jakościowy i pamiętam, że wielu poważnych ludzi pracowało na komputerach z kartami Hercules. Obraz graficzny wyglądał tak:

Karta Hercules była monochromatyczna, tzn. w trybie graficznym punkty albo były czarne, albo jasne. Jeden piksel w pamięci karty reprezentowany był jednym bitem. Ramka obrazu graficznego zajmowała zatem 720 / 8 x 348 = 31320 bajtów. Na Herculesa powstało w swoim czasie mnóstwo programów, w tym dużo gier. Mam do tej karty sentyment, ponieważ mój pierwszy IBM PC był w nią wyposażony. Miałem też monitor monochromatyczny amber (bursztynowy), którego obraz nie męczył wzroku i można było przy nim pracować godzinami.

Tryb monochromatyczny działa na dosyć prostej zasadzie. Obraz również zbudowany jest z linii. W buforze ekranu przechowywane są bajty zawierające kolejne piksele linii obrazu. Pojedynczy bit zawiera informację o kolorze (a właściwie o jasności) odpowiadającego mu piksela. Np. bit 0 może oznaczać piksel zgaszony, a 1 zaświecony. Układ graficzny pobiera z bufora kolejne bajty linii obrazu, przetwarza w sygnał obrazowy, a następnie przesyła wyjście do monitora monochromatycznego.

Tryb paletowy

Aby stosunkowo niskim kosztem wprowadzić grafikę kolorową, wymyślono tryb paletowy. Działał on w ten sposób, iż w pamięci ekranu piksele reprezentowane były przez grupy bitów. Załóżmy, że piksel będzie definiowany przez dwa sąsiadujące ze sobą bity. W każdym bajcie jest 8 bitów b0 ... b7, zatem jeden bajt pamięci ekranu może pomieścić cztery piksele p0 ...p3:

Bajt obrazu
p3 p2 p1 p0
b7 b6 b5 b4 b3 b2 b1 b0

Piksel p0 definiują bity b0 i b1.
Piksel p1 definiują bity b2 i b3.
Piksel p2 definiują bity b4 i b5.
Piksel p3 definiują bity b6 i b7.

 

Jeśli potraktujemy te dwa bity każdego piksela jako liczbę dwójkową, to otrzymamy cztery możliwe wartości: 00 = 0, 01 = 1, 10 = 2, 11 = 3. Wartości te są numerami kolorów palety. Kolory palety można przydzielać z szerokiego zestawu kolorów. W ten sposób jednocześnie na obrazku mogą być cztery wybrane kolory, a obrazek nie zajmuje zbyt wiele miejsca w pamięci. Na przykład obrazek o rozdzielczości 320 x 240 pikseli (poziom, pion) zajmie w trybie paletowym o 4 kolorach (czyli po 2 bity na piksel, 4 piksele na bajt): 320 / 4 x 240 = 19200 bajtów.

 

 

Tryb paletowy działa podobnie do trybu monochromatycznego. Oprócz bufora ekranu układ graficzny posiada dostęp do palety kolorów, w której są zdefiniowane kolory dla poszczególnych kombinacji bitów pikseli. Kolory te mogą być ustalone na stałe lub definiowane przez użytkownika, co daje więcej możliwości. Obraz zbudowany jest z linii. W buforze ekranu przechowywane są bajty kolejnych linii obrazu. W bajtach tych piksele zajmują określoną liczbę bitów (w powyższym przykładzie każdy piksel to dwa bity). Układ graficzny pobiera kolejne bajty linii obrazu z bufora. Następnie z pobranych bajtów wydziela bity poszczególnych pikseli. Na podstawie tych bitów pobiera z palety odpowiedni kolor piksela i przesyła go na wyjście do monitora kolorowego.

Przykład obrazka z paletą 4 kolorów:

Paleta:
00 01 10 11

Obrazek:

00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 11 11 11 11 11 11 00 00 00
00 00 00 00 00 11 11 11 11 11 11 11 11 11 00 00
00 00 00 00 11 11 11 11 11 11 11 11 11 11 00 00
00 00 00 00 11 11 11 11 11 11 11 11 11 11 11 00
00 00 00 00 00 11 11 11 11 11 11 11 11 11 00 00
01 01 01 01 01 01 01 11 11 11 11 11 11 01 01 01
01 01 01 01 01 01 01 01 01 10 10 01 01 01 01 01
01 01 01 01 01 01 01 01 01 10 10 01 01 01 01 01
01 01 01 01 01 01 01 01 01 10 10 01 01 01 01 01
01 01 01 01 01 01 01 01 01 10 10 01 01 01 01 01
01 11 11 11 11 11 11 11 11 10 10 11 11 11 11 11
11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11
11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11
11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11

 

Jeśli chcemy zwiększyć liczbę kolorów, to zwiększamy ilość bitów w grupie piksela. Dla 4 bitów (grupy 3-bitowe nie mieszczą się ładnie w bajcie: 2-3-3, dlatego takiego przydziału zwykle się nie stosuje) będzie to 16 różnych kolorów, dla 8 bitów (piksel zajmuje cały bajt) możemy mieć 256 różnych kolorów. Typowym przykładem obrazków paletowych jest format GIF, który przechowuje paletę do 256 kolorów, a piksele mogą zawierać do 8 bitów indeksujących kolory palety. 256 kolorów zwykle wystarcza do tworzenia realistycznie wyglądających obrazów:

Zaletą trybu paletowego jest mały rozmiar bufora ekranu, co było korzystne dla wolnych procesorów. Dzisiaj te tryby w komputerach IBM PC mają znaczenie prawie wyłącznie historyczne, chociaż wciąż są obecne na kartach graficznych dla kompatybilności wstecznej.

Karta graficzna CGA

Pierwszą kartą graficzną stosującą kolor w trybie paletowym była karta CGA (ang. Color Graphic Adapter) zwana popularnie cegłą.  Posiadała następujące parametry:

Tryby tekstowe (znaki w matrycy 8 x 8 pikseli):

40 kolumn x 25 wierszy, 16 kolorów tekstu i tła
80 kolumn x 25 wierszy, 16 kolorów tekstu i tła

 

Tryby graficzne:

320 x 200 pikseli w ustalonych 4 kolorach (2 bity na piksel):

 

640 x 200 w dwóch kolorach:

 

160 x 100 w 16 kolorach:

 

Wg dzisiejszych standardów jakość tych obrazów jest tragiczna, ale pamiętaj, że to były lata 80-te i ludzie się tym zachwycali.

Karta graficzna EGA

Na potrzeby bardziej wymagających użytkowników opracowano lepszą kartę graficzną o nazwie EGA (ang. Enhanced Graphic Adapter). Obraz wciąż był tworzony w trybie paletowym, jednak EGA rezerwowała na piksel 4 bity, co umożliwiało stosowanie wyświetlania pikseli w 16 kolorach z palety 64 kolorów. Parametry karty EGA były następujące:

Tryby tekstowe:

40 kolumn x 25 wierszy, znaki w matrycy 8 x 8, 16 kolorów
80 kolumn x 25 wierszy, znaki w matrycy 8 x 8, 16 kolorów
80 kolumn x 25 wierszy, znaki w matrycy 8 x 14 (lepsza czytelność liter), 16 kolorów
80 kolumn x 43 wiersze, znaki w matrycy 8 x 8, 16 kolorów

Tryby graficzne:

640 x 350, 16 kolorów z palety 64 kolorów

 

640 x 350 w trybie monochromatycznym
640 x 200 w 16 kolorach
320 x 200 w 16 kolorach

Obraz generowany przez kartę EGA jest bez porównania lepszy od obrazu możliwego do uzyskania na karcie CGA. Jednak koszt karty EGA w latach 80-tych był na poziomie zaporowym, tylko nieliczni mogli sobie pozwolić na to cudo. Do tego dochodził jeszcze niebagatelny koszt kolorowego monitora.

Pod koniec lat 80-tych pojawiła się karta VGA (ang. Video Graphic Array). Wciąż pracowała w trybach paletowych, jednak kolory palety mogły być dowolnie definiowane przez określanie intensywności barw podstawowych (czerwonej, zielonej i niebieskiej) z rozdzielczością 6 bitów (wartości od 0 do 63). W efekcie na karcie VGA kolory palety mogły być wybierane z puli 64 x 64 x 64 = 262144 kolorów. Parametry VGA były następujące:

Tryby tekstowe:

40 kolumn x 25 wierszy, znaki w matrycy 9 x 16, 16 kolorów
80 kolumn x 25 wierszy, znaki w matrycy 9 x 16, 16 kolorów
80 kolumn x 43 wiersze, znaki w matrycy 8 x 8, 16 kolorów, kompatybilność z EGA
80 kolumn x 50 wiersze, znaki w matrycy 8 x 8, 16 kolorów

Tryby graficzne:

640 x 480 w 16 kolorach
640 x 350 w 16 kolorach, kompatybilność z EGA
640 x 200 w 16 kolorach, kompatybilność z EGA
320 x 200 w 16 kolorach, kompatybilność z EGA
320 x 200 w 256 kolorach

Karty Hercules, CGA, EGA i VGA wytyczają drogę rozwoju grafiki komputerowej. Dla porównania przedstawiamy ten sam obrazek wyświetlany na każdej z tych kart:


Hercules, obraz monochromatyczny

CGA, obraz w 4 ustalonych kolorach

EGA, obraz w 16 wybranych kolorach


VGA, obraz w 256 definiowanych kolorach

Poczuj teraz zazdrość tych, którzy w latach 80-tych mieli komputery z kartą Hercules, a widzieli obraz generowany przez VGA, która w trybie 256 kolorów potrafiła wyświetlać grafikę prawie w jakości fotograficznej. Zostawmy to historykom. Karta VGA kończy czasy trybów paletowych, lecz pamiętaj, że współczesne karty graficzne są kompatybilne w dół z kartą VGA. Na przykład, Windows, jeśli nie potrafi rozpoznać karty graficznej komputera, na którym pracuje, to włącza tryb paletowy VGA w 16 kolorach (również robi tak w trybie awaryjnym, gdy są problemy z obsługą ekranu). Zakłada się bowiem, że każda karta graficzna ten tryb posiada. Jak widzisz, nie jest to zupełna staroć i czasem się z VGA spotkasz.

Karta graficzna SVGA/UVGA

Na początku lat 90-tych pojawiły się karty graficzne, które dodawały do trybów VGA nowe tryby o większej rozdzielczości i większej liczbie kolorów. Nazywano je kartami SVGA (ang. Super Video Graphic Array) lub UVGA (ang. Ultra Video Graphic Array). Początkowo były to rozszerzone tryby paletowe, np. obraz w rozdzielczości 1024 x 768 pikseli w 256 kolorach. Jednak wkrótce pojawił się nowy tryb graficzny, zwany Hi-color. W trybie tym każdy piksel w buforze ekranu składał się z 16 bitów. Bity te podzielone były na 3 grupy: 5-6-5 (w niektórych rozwiązaniach ograniczano się do 15 bitów: 5-5-5). Każda grupa określała jasność barwy składowej czerwonej, zielonej i niebieskiej. Grupy były traktowane jako liczby w kodzie NBC, co daje dla 5 bitów wartości od 0 do 31 (kolor czerwony i niebieski), a dla 6 bitów dostajemy wartości od 0 do 63 (kolor zielony). W efekcie piksel definiuje swój własny kolor, nie potrzebuje palety. Wszystkich kolorów jest 32 x 64 x 32 = 65536. Takie rozwiązanie upraszcza tworzenie obrazu. Oczywiście zwiększa również zapotrzebowanie na pamięć. Np. obraz o rozdzielczości 1024 x 768 pikseli w trybie Hi-color wymaga 1024 x 768 x 2 = 1572864 bajtów, czyli około 1,5 MB. Również moc procesora musi być odpowiednio duża, aby zarządzać płynnie przepływem takiej ilości danych. Dla kart SVGA opracowano specjalne magistrale umożliwiające szybki transfer informacji (VESA i PCI). Pamiętam, jak w latach 90-tych po raz pierwszy zobaczyłem komputer z kartą SVGA, która płynnie przewijała arkusz kalkulacyjny. To było coś. Dzisiaj wszyscy traktują takie rzeczy za oczywiste.

W trybie Hi-color można wyświetlać obrazy w jakości bliskiej fotografii. Poniżej mamy obrazek prezentujący możliwe do wyświetlenia kolory:

 

Konsekwencją trybu hi-color było powstanie trybu RGB. Tutaj każdy piksel był reprezentowany w pamięci obrazu przez 3 bajty. Bajty te zawierały informację o intensywności poszczególnych barw składowych czerwonej, zielonej i niebieskiej. Bajt to 8 bitów i w kodzie NBC przyjmuje wartości od 0 do 255. Daje to 256 różnych poziomów jasności dla każdej barwy składowej. Wszystkich kolorów będzie zatem 256 x 256 x 256 = 16777216, czyli ponad 16 milionów. Oko człowieka rozpoznaje podobno co najwyżej 4 miliony kolorów, zatem tryb RGB potrafi wyświetlać obrazy w pełnej jakości fotograficznej. Dlatego często nazywa się go True Color, co po angielsku znaczy prawdziwy kolor.

Wkrótce karty zaczęto wyposażać w dodatkowe układy, w tzw. akceleratory graficzne. Są to w zasadzie specjalizowane, szybkie, wielowątkowe komputery, a ich zadaniem jest bardzo szybka obróbka danych graficznych i odciążenie procesora głównego, który w tym czasie może zająć się czymś innym. Dzięki akceleratorom powstają ruchome obrazy przestrzenne o wysokiej jakości. Bez nich nie istniałyby współczesne gry 3D.

 

Tworzenie obrazu

Współczesne karty graficzne są układami bardzo skomplikowanymi. Zwykły użytkownik nie ma potrzeby wnikania w szczegóły technicznych rozwiązań zastosowanych w jego karcie graficznej. Ponieważ karty graficzne posiadają przeróżne parametry, dostęp do nich z poziomu programu odbywa się za pomocą tzw. sterownika (ang. driver). Sterownik jest biblioteką funkcji, które tworzy producent karty graficznej, a które następnie instaluje się w systemie operacyjnym. Komunikacja ze sterownikiem odbywa się wg standardowych, ustalonych reguł. Natomiast sterownik przekształca otrzymane polecenia na odpowiednie działania na danej karcie graficznej. Dzięki temu rozwiązaniu ten sam program może komunikować się z dowolną kartą graficzną, dla której istnieje sterownik graficzny. Oczywiście nie wszystkie karty są w stanie realizować pełen zbiór wszystkich możliwych operacji graficznych. Zwykle sterownik umożliwia poznanie możliwości karty graficznej i program może dostosować do tego swoje działanie. Zatem nie będziemy wchodzić w szczegóły techniczne, lecz podamy ogólne zasady współpracy z kartą grafiki.

Obraz jest tworzony w postaci siatki pikseli zwanych rastrem.

Rozdzielczość (ang. resolution) określa liczbę kolumn i wierszy rastra. Zwykle jest to co najmniej 1024 x 768 (1024 piksele w każdej z 768 linii obrazu). Im większa rozdzielczość, tym więcej szczegółów zawiera obraz.

Głębia koloru (ang. color depth) określa ilość bitów na jeden piksel obrazu. Zwykle będzie to 32 bity (karty stosujące 24 bity na piksel należą dzisiaj już do historii).

Kolor piksela określany jest przez jasność trzech kolorów składowych: czerwonego (ang. R red), zielonego (ang. G Green) i niebieskiego (ang. B Blue). Mieszanka tych kolorów daje wszystkie barwy, które mogą przyjmować piksele. Informacja o jasności barw podstawowych zawarta jest w kodzie piksela. Jeśli piksel ma rozmiar 4 bajty (32 bity), to zwykle pierwsze trzy bajty lub trzy ostatnie definiują składowe koloru R, G i B, a czwarty bajt jest albo nieużywany, albo zawiera informację o stopniu przezroczystości piksela (tzw. kanał Alfa). Omówimy to dokładniej w kolejnych rozdziałach.

Uwaga: kolejność bajtów może być odwrotna. Zależy to od rozwiązań technicznych w konkretnym komputerze. Powyżej jest tylko przykład poglądowy.

32-bitowy rozmiar pikseli jest wygodny dla współczesnych procesorów, które umożliwiają jednoczesny dostęp do danych 32 bitowych w pamięci, jeśli te dane leżą w komórkach, z których pierwsza ma adres podzielny przez 4. Wiąże się to ze sposobem dostępu do pamięci przez taki procesor.

Bufor ekranu (ang. frame buffer), to obszar pamięci, w którym jest zawarta treść obrazu. Współczesne karty graficzne posiadają własną pamięć wewnętrzną, w której przechowują wyświetlany obraz. Zwykle w pamięci głównej RAM tworzy się pomocniczy bufor, do którego ma dostęp procesor. Obraz jest tworzony w tym pomocniczym buforze, a gdy zostanie w całości ukończony, jest przesyłany do bufora karty (niektóre karty graficzne współdzielą pamięć z procesorem komputera – szczególnie to dotyczy akceleratorów graficznych zintegrowanych z procesorami Intela). Dzięki temu wyświetlany obraz nie jest zakłócany.

Zatem aby obraz pojawił się na ekranie, należy go utworzyć w buforze ekranu, a następnie przesłać zawartość bufora do pamięci karty. Często operacje takie wykonuje akcelerator karty graficznej, jeśli bufor znajduje się w pamięci karty (tzw. VRAM, czyli Video RAM), co znakomicie przyspiesza wyświetlanie grafiki. Wersje 64 bitowe procesorów Intela (innych firm też, np. AMD) są wyposażane w układ akceleratora grafiki, który potrafi samodzielnie tworzyć obraz graficzny o wysokiej rozdzielczości. Użyty akcelerator może nie jest najwyższych lotów, ale do prostych zadań graficznych (wyświetlanie filmów HD, animacje 3D)  jest zupełnie wystarczający. Przy bardziej wymagających zadaniach, np. do tworzenia zaawansowanej grafiki trójwymiarowej, potrzebna jest zwykle porządna karta graficzna, niestety, kosztuje kilkaset złotych (a nawet kilka tysięcy, taka z górnej półki). Oczywiście, aby mieć jakikolwiek zysk z zakupu takiej karty, musisz również posiadać wydajny procesor (np. i7) i dużo szybkiej pamięci RAM w komputerze (o monitorze odpowiedniej jakości już nie wspomnę). Na szczęście zabawę z grafiką można uprawiać nawet na prostym sprzęcie.

Znając rozdzielczość i głębię koloru można łatwo wyliczyć rozmiar bufora ekranowego. Na przykład obraz o rozdzielczości 1200 x 1024 x 32 bity zajmie w pamięci RAM obszar o wielkości 1200 x 1024 x 32 / 8 = 4915200 bajtów, czyli około 5 MB. Dla dzisiejszych procesorów 64-bitowych nie jest to żaden problem.

W kolejnych rozdziałach wykonasz odpowiednie ćwiczenia, które przybliżą ci te zagadnienia. Zapraszam

 

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.