Zanim przystąpisz do tej lekcji, zapoznaj się z poprzednimi lekcjami:

Wstęp do grafiki - biblioteki SDL i newgfx
Opis biblioteki newgfx
Grafika - wielokąty
Tworzenie wykresów funkcji


Grafika SLD

Obcinanie obszarów

Obcinanie (ang. clipping) ma na celu ograniczanie rysowania grafiki do wybranego obszaru na powierzchni graficznej. Fragmenty rysowanych figur, które znajdą się poza obszarem obcinania (ang. clip region), nie są rysowane.

 

obrazek
Bez obcinania
obrazek

Z obcinaniem

Obszar obcinania jest określony za pomocą struktury SDL_Rect:

 

typedef struct
{
  Sint16 x, y;
  Uint16 w, h;
} SDL_Rect;

 

Prostokąt obcinający zawarty jest w strukturze SDL_Surface. Standardowo ma on wymiary ekranu. Większość funkcji reagujących na ten prostokąt ma w swojej nazwie słowo Clip. Rozwiązanie takie przyjęto z powodu zmniejszenia szybkości działania tych funkcji w stosunku do wersji bez obcinania. Obcinanie gwarantuje, iż grafika nie wyjdzie poza zdefiniowany obszar.

Prostokąt obcinania ustawiamy za pomocą funkcji:

 

void SDL_SetClipRect(SDL_Surface * surface, SDL_Rect * clip);

 

Jeśli drugi parametr ma wartość NULL, to jako prostokąt obcinający zostanie ustawiony cały ekran. Poniższy program rysuje serię przypadkowych linii, które zostają obcięte do zdefiniowanego wcześniej prostokąta:

 

// Obcinanie
// (C)2011 Koło Informatyczne
// I LO w Tarnowie
//-------------------------------

#include "newgfx.h"
#include <cstdlib>
#include <time.h>

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

  if(!SDL_Init(SDL_INIT_VIDEO))
  {
    atexit(SDL_Quit);

    SDL_Surface * screen = SDL_SetVideoMode(320, 240, 32, SDL_HWSURFACE);

    SDL_Rect * r = new SDL_Rect;

    // Inicjujemy generator liczb pseudolosowych

    srand(time(NULL));

    // Ustawiamy prostokat obcinania

    r->x = r->y = 20;
    r->w = screen->w - 40;
    r->h = screen->h - 40;

    SDL_SetClipRect(screen,r);

    // Zwiększamy prostokąt o 1 piksel w każdym kierunku

    r->x--; r->y--; r->w += 2; r->h += 2;

    if(SDL_MUSTLOCK(screen)) SDL_LockSurface(screen);

    // Rysujemy ramkę wokół prostokata obcinania

    gfxRect(screen,r,0xffff00);

    // w prostokącie obcinania rysujemy 100 przypadkowych linii

    for(int i = 0; i < 100; i++)
    {
        Sint32 xp = rand() % screen->w;
        Sint32 xk = rand() % screen->w;
        Sint32 yp = rand() % screen->h;
        Sint32 yk = rand() % screen->h;

        gfxClipLine(screen,xp,yp,xk,yk,0xffffff);
    }

    if(SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen);

    SDL_UpdateRect(screen, 0, 0, 0, 0);

    // oczekujemy klawisza ESC

    int waiting = 0;

    do
    {
      SDL_Event event;

      while (SDL_PollEvent(&event))
        if ((event.type == SDL_QUIT) ||
           ((event.type == SDL_KEYDOWN) &&
            (event.key.keysym.sym == SDLK_ESCAPE))) waiting = 1;
    } while(!waiting);
  }
  return 0;
}

 

obrazek

 

Zmieniając nieco pętlę for, możemy rysować różne figury w prostokącie obcinania:

 

Okręgi:

 

...
    for(int i = 0; i < 100; i++)
    {
        Sint32 x = rand() % screen->w;
        Sint32 y = rand() % screen->h;
        Uint32 p = 20 + rand() % 100;

        gfxClipCircle(screen,x,y,p,0xffffff);
    }
...

 

obrazek

 

Koła (uwaga: funkcje wypełniające figury automatycznie reagują na prostokąt obcinania i dlatego nie mają w swojej nazwie słowa Clip):

 

...
    for(int i = 0; i < 100; i++)
    {
        Sint32 x = rand() % screen->w;
        Sint32 y = rand() % screen->h;
        Uint32 p = 5 + rand() % 30;
        Uint32 c = ((rand() % 256) << 16) | ((rand() % 256) << 8) | (rand() % 256);

        gfxFillCircle(screen,x,y,p,c);
    }
...

 

obrazek

 

Elipsy:

 

...
    for(int i = 0; i < 100; i++)
    {
        Sint32 x = rand() % screen->w;
        Sint32 y = rand() % screen->h;
        Uint32 px = 5 + rand() % 30;
        Uint32 py = 5 + rand() % 30;

        gfxClipEllipse(screen,x,y,px,py,0xffffff);
    }
...

 

obrazek

 

Owale:

 

...
    for(int i = 0; i < 100; i++)
    {
        Sint32 x = rand() % screen->w;
        Sint32 y = rand() % screen->h;
        Uint32 px = 5 + rand() % 30;
        Uint32 py = 5 + rand() % 30;
        Uint32 c = ((rand() % 256) << 16) | ((rand() % 256) << 8) | (rand() % 256);

        gfxFillEllipse(screen,x,y,px,py,c);
    }
...

 

obrazek

 

Przezroczystość

Prawie każda funkcja graficzna biblioteki newgfx potrafi również rysować z efektem przezroczystości. W tym celu w wywołaniu funkcji dodajemy jeszcze jeden parametr o zakresie od 0 (obiekt zupełnie przezroczysty) do 256 (obiekt zupełnie nieprzezroczysty). Ostatni program z efektem przezroczystości wygląda następująco:

 

Owale przezroczyste:

 

...
    for(int i = 0; i < 100; i++)
    {
        Sint32 x = rand() % screen->w;
        Sint32 y = rand() % screen->h;
        Uint32 px = 5 + rand() % 30;
        Uint32 py = 5 + rand() % 30;
        Uint32 c = ((rand() % 256) << 16) | ((rand() % 256) << 8) | (rand() % 256);

        gfxFillEllipse(screen,x,y,px,py,c,196);
    }
...

 

obrazek

 

Łamane

Biblioteka newgfx zawiera wygodne funkcje do rysowania dowolnych łamanych:

 

void gfxClipPoly(SDL_Surface * s, Sint32 * f, Uint32 color);
void gfxClipPoly(SDL_Surface * s, Sint32 * f, Uint32 color, Uint32 alpha);
void gfxPoly(SDL_Surface * s, Sint32 * f, Uint32 color);
void gfxPoly(SDL_Surface * s, Sint32 * f, Uint32 color, Uint32 alpha);
void gfxClipWuPoly(SDL_Surface * s, Sint32 * f, Uint32 color);        // łamana z wygładzonymi liniami
void gfxWuPoly(SDL_Surface * s, Sint32 * f, Uint32 color);

 

oraz łamanych wypełnionych (powinny być zamknięte):

 

void gfxFillPoly(SDL_Surface * s, Sint32 * f, Uint32 color);
void gfxFillPoly(SDL_Surface * s, Sint32 * f, Uint32 color, Uint32 alpha);

s - powierzchnia graficzna
f - definicja figury
color - kolor linii lub wypełnienia
alpha - przezroczystość

 

Kształt łamanej zdefiniowany jest w tablicy f, której wskaźnik przekazujemy do funkcji. Każda łamana rozpoczyna się od liczby określającej ilość kolejnych wierzchołków. Jeśli liczba ta ma wartość 0, to oznacza to koniec listy łamanych. Za liczbą wierzchołków następują pary współrzędnych x,y określające położenie wierzchołków na płaszczyźnie rysunkowej.

Przykład:

        3 12 1 14 5 1 1 2 5 3 4 7 0

Ta lista definiuje dwie łamane:

         3  12 1 14 5 1 1       2  5 3 4 7     0

Pierwsza łamana posiada trzy wierzchołki: (12,1), (14,5) i (1,1).
Druga łamana posiada tylko dwa wierzchołki: (5,3) i (4,7).
Ostatnie zero oznacza koniec listy łamanych.

 

Listę łamanych można utworzyć w tablicy o odpowiedniej wielkości. Na przykład tak:

 

Sint32 f[] = {3, 12, 1, 14, 5, 1, 1, 2, 5, 3, 4, 7, 0};

 

Dla przykładu zdefiniujmy następującą figurę:

 

obrazek

 

Figura składa się z dwóch łamanych zamkniętych - wymaga to powtórzenie na początku i na końcu pierwszego wierzchołka. Zatem łamana zewnętrzna posiada 5 wierzchołków, a łamana wewnętrzna ma ich 4. Pełna definicja wygląda następująco:

 

5 20 10 110 50 20 90 10 50 20 10
4 30 30 80 50 30 70 30 30
0

 

// Łamane
// (C)2011 Koło Informatyczne
// I LO w Tarnowie
//-------------------------------

#include "newgfx.h"

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

  if(!SDL_Init(SDL_INIT_VIDEO))
  {
    atexit(SDL_Quit);

    SDL_Surface * screen = SDL_SetVideoMode(320, 240, 32, SDL_HWSURFACE);

    // Tworzymy tablicę z listą łamanych

    Sint32 f[] = {5,20,10,110,50,20,90,10,50,20,10,
                  4,30,30,80,50,30,70,30,30,
                  0};

    if(SDL_MUSTLOCK(screen)) SDL_LockSurface(screen);

    // Rysujemy wypełnienie łamanej w kolorze czerwonym

    gfxFillPoly(screen,f,0xff0000);

    // Na wypełnieniu rysujemy obrys łamanej w kolorze białym

    gfxPoly(screen,f,0xffffff);

    if(SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen);

    SDL_UpdateRect(screen, 0, 0, 0, 0);

    // oczekujemy klawisza ESC

    int waiting = 0;

    do
    {
      SDL_Event event;

      while (SDL_PollEvent(&event))
        if ((event.type == SDL_QUIT) ||
           ((event.type == SDL_KEYDOWN) &&
            (event.key.keysym.sym == SDLK_ESCAPE))) waiting = 1;
    } while(!waiting);
  }
  return 0;
}

 

obrazek

 

Wypełnianie obszarów

Biblioteka newgfx posiada dwie funkcje do wypełniania dowolnych obszarów:

 

void gfxBoundaryFill(SDL_Surface * screen, Sint32 x, Sint32 y, Uint32 fcolor, Uint32 bcolor);
void gfxFloodFill(SDL_Surface * screen, Sint32 x, Sint32 y, Uint32 fcolor);

 

W obu przypadkach podajemy współrzędne punktu (x,y) wewnątrz obszaru, który ma zostać wypełniony kolorem fcolor.

Pierwsza funkcja wykonuje tzw. wypełnianie konturowe (ang boundary-fill) - wszystkie piksele zawarte wewnątrz konturu (zamkniętej linii zbudowanej z pikseli o tym samym kolorze) zostają pokolorowane na ten sam kolor wypełnienia. Zawartość objętego konturem obszaru nie jest istotna (patrz poniżej). Nie może on jedynie zawierać pikseli o kolorze wypełnienia. Kolor konturu określa parametr bcolor.

obrazek           obrazek

Wypełnianie powodziowe (ang. floodfill) - wszystkie piksele spójne (tzn. przyległe w 4 lub 8 kierunkach) do piksela startowego i posiadające ten sam co on kolor zostaną pokolorowane na nowy kolor wypełnienia. Wynika z tego, iż wypełniany obszar musi posiadać przed operacją jednolity kolor. Natomiast obszary przyległe mogą posiadać kolory dowolne, ale różne od koloru wypełnianego obszaru.

obrazek           obrazek

Piksel, od którego rozpoczynamy wypełnianie obszaru nazywamy ziarnem wypełnienia (ang. fill seed). Musi on znajdować się wewnątrz wypełnianego obszaru. To właśnie jego położenie określają współrzędne x,y.

Obie procedury reagują na prostokąt obcinania.

Poniższy program najpierw rysuje zielone okręgi, a na nich czerwone prostokąty. Następnie wypełnia tło kolorem niebieskim wykorzystując wypełnienie konturowe. Za kontur przyjmujemy kolor czerwony - czyli kolor prostokątów. Zwróć uwagę, że okręgi poza prostokątami zostaną zamalowane - zniknie wszystko aż do napotkania konturu czerwonego.

 

// Wypełnienie konturowe
// (C)2011 Koło Informatyczne
// I LO w Tarnowie
//-------------------------------

#include "newgfx.h"
#include <cstdlib>
#include <time.h>

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

  if(!SDL_Init(SDL_INIT_VIDEO))
  {
    atexit(SDL_Quit);

    SDL_Surface * screen = SDL_SetVideoMode(320, 240, 32, SDL_HWSURFACE);

    srand(time(NULL));

    if(SDL_MUSTLOCK(screen)) SDL_LockSurface(screen);

    // rysujemy okręgi w kolorze zielonym

    for(int i = 0; i < 10; i++)
      gfxClipCircle(screen, rand() % screen->w, rand() % screen->h, 10 + rand() % 50, 0x00ff00);

    // rysujemy ramki w kolorze czerwonym

    SDL_Rect * r = new SDL_Rect;

    for(int i = 0; i < 10; i++)
    {
        r->x = rand() % screen->w;
        r->y = rand() % screen->h;
        r->w = 20 + rand() % 50;
        r->h = 20 + rand() % 50;
        gfxClipRect(screen,r,0xff0000);
    }

    // wypełniamy od środka ekranu kolorem niebieskim do konturu czerwonego

    gfxBoundaryFill(screen, screen->w >> 1, screen->h >> 1, 0x0000ff, 0xff0000);

    if(SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen);

    SDL_UpdateRect(screen, 0, 0, 0, 0);

    // oczekujemy klawisza ESC

    int waiting = 0;

    do
    {
      SDL_Event event;

      while (SDL_PollEvent(&event))
        if ((event.type == SDL_QUIT) ||
           ((event.type == SDL_KEYDOWN) &&
            (event.key.keysym.sym == SDLK_ESCAPE))) waiting = 1;
    } while(!waiting);
  }
  return 0;
}

 

obrazek

 

Wymień teraz w programie funkcję gfxBondaryFill() na:

 

gfxFloodFill(screen, screen->w >> 1, screen->h >> 1, 0x0000ff);

 

Teraz efekt wypełnienia będzie inny - funkcja gfxFloodFill() reaguje na każdy kolor, który różni się od koloru ziarna:

 

obrazek

 

Poniższy program pozwala śledzić wypełnianie obszarów za pomocą funkcji gfxFloodFill(). W programie zrealizowany jest algorytm wypełniania. Wykorzystuje on stos do przechowywania pikseli wypełnianego obszaru:

 

// Wizualizacja algorytmu wypełniania powodziowego
// (C)2011 Koło Informatyczne
// I LO w Tarnowie
//-------------------------------

#include <windows.h>
#include "newgfx.h"
#include <cmath>      // funkcja sqrt
#include <cstdlib>
#include <time.h>     // do inicjalizacji generatora pseudolosowego
#include <stack>      // używamy STL do implementacji stosu

using namespace std;  // potrzebne dla STL!

const int SCRX = 640; // stałe określające szerokość i wysokość
const int SCRY = 480; // ekranu w pikselach

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

    if(SDL_Init(SDL_INIT_VIDEO)) exit(-1);

    atexit(SDL_Quit);

    SDL_Surface * screen;

    screen = SDL_SetVideoMode(SCRX, SCRY, 32, SDL_HWSURFACE);

    // inicjujemy generator liczb pseudolosowych

    srand((unsigned)time(NULL));

    // wyznaczamy środek ekranu

    Sint32 x = screen->w >> 1;
    Sint32 y = screen->h >> 1;

    if(SDL_MUSTLOCK(screen)) SDL_LockSurface(screen);

    // rysujemy 20 losowych ramek w różnych kolorach, tak aby nie przykrywały środka ekranu

    SDL_Rect * r = new SDL_Rect;
    Uint32 color;

    for(int i = 0; i < 20; i++)
    {
        do
        {
            r->x = rand() % screen->w;
            r->y = rand() % screen->h;
            r->w = rand() % (screen->w >> 3) + 4;
            r->h = rand() % (screen->h >> 3) + 4;

        } while((r->x <= x) && (x < r->x + r->w) && (r->y <= y) && (y < r->y + r->h));

        color = ((rand() % 256) << 16) | ((rand() % 256) << 8) | (rand() % 256);
        gfxClipRect(screen, r, color);
    }

    // rysujemy 20 losowych okręgów w różnych kolorach, tak aby nie przykrywały środka

    Sint32 xs, ys, rs;

    for(int i = 0; i < 20; i++)
    {
        do
        {
            xs = rand() % screen->w;
            ys = rand() % screen->h;
            rs = rand() % (screen->w >> 3) + 4;
        } while(sqrt((x - xs) * (x - xs) + (y - ys) * (y - ys)) <= rs);

        color = ((rand() % 256) << 16) | ((rand() % 256) << 8) | (rand() % 256);

        gfxClipCircle(screen,xs,ys,rs,color);
    }

    if(SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen);

    SDL_UpdateRect(screen, 0, 0, 0, 0);

    // tutaj umieszczamy algorytm wypełniania

    Uint32 scolor;             // kolor ziarna
    Uint32 fcolor = 0x0000ff;  // kolor wypełnienia
    Uint32 pcolor;             // kolor piksela
    stack<Sint32> q;           // stos

    if(SDL_MUSTLOCK(screen)) SDL_LockSurface(screen);

    scolor = gfxGetPixel(screen, x, y);

    if(SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen);

    q.push(x); q.push(y);

    while(!q.empty())
    {
        y = q.top(); q.pop(); // pobieramy ze stosu x i y
        x = q.top(); q.pop();
        if((x < screen->w) && (x >= 0) && (y < screen->h) && (y >= 0))
        {
            if(SDL_MUSTLOCK(screen)) SDL_LockSurface(screen);

            pcolor = gfxGetPixel(screen, x, y);

            if(SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen);

            if(pcolor != scolor) continue;

            if(SDL_MUSTLOCK(screen)) SDL_LockSurface(screen);

            gfxPlot(screen, x, y, fcolor);

            if(SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen);

            SDL_UpdateRect(screen, x, y, 1, 1);

            q.push(x); q.push(y - 1);
            q.push(x + 1); q.push(y);
            q.push(x); q.push(y + 1);
            q.push(x - 1); q.push(y);
        }
    }

    // oczekujemy klawisza ESC

    int waiting = 0;

    do
    {
      SDL_Event event;

      while (SDL_PollEvent(&event))
        if ((event.type == SDL_QUIT) ||
           ((event.type == SDL_KEYDOWN) &&
            (event.key.keysym.sym == SDLK_ESCAPE))) waiting = 1;
    } while(!waiting);
  return 0;
}

 


   I Liceum Ogólnokształcące   
im. Kazimierza Brodzińskiego
w Tarnowie

©2024 mgr Jerzy Wałaszek

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji
GNU Free Documentation License.

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

W artykułach serwisu są używane cookies. Jeśli nie chcesz ich otrzymywać,
zablokuj je w swojej przeglądarce.
Informacje dodatkowe