Nici z grafiki

W ubiegłym miesiącu wykreślaliśmy obrazy słynnego fraktala Mandelbrota i nadal czekamy na wasze prace. Najciekawsze programy wkrótce opiszemy i zamieścimy na krążku CD, a ich autorów nagrodzimy drobnymi upominkami. Dzisiaj zajmiemy się znacznie prostszymi algorytmami, do których realizacji wystarczy umiejętność wykreślania kolorowej linii na ekranie.

W ubiegłym miesiącu wykreślaliśmy obrazy słynnego fraktala Mandelbrota i nadal czekamy na wasze prace. Najciekawsze programy wkrótce opiszemy i zamieścimy na krążku CD, a ich autorów nagrodzimy drobnymi upominkami. Dzisiaj zajmiemy się znacznie prostszymi algorytmami, do których realizacji wystarczy umiejętność wykreślania kolorowej linii na ekranie.

Dzisiaj zajmiemy się wyjątkowo prostym zagadnieniem - kreśleniem kolorowych linii. Jak wiadomo, wykreślana na ekranie linia ma swój początek, koniec i kolor. Załóżmy, że funkcja dostarczająca linii rozpiętej między pikselami (x1,y1) i (x2,y2) w kolorze kolor ma następującą postać:

line( x1, y1, x2, y2, kolor);

W języku programowania, którym się posługujecie, zapewne wygląda to nieco inaczej, np. w C++ Builderze lub w Delphi kolorowej linii dostarczy sekwencja wywołań:

Canvas -> Pen -> Color = kolor;

Canvas -> MoveTo( x1, y1);

Canvas -> LineTo( x2, y2);

Rysunek 1. Grafika ''Motyl'' to 50 linii, rozpiętych na 100 ''gwoździkach''. ''Gwoździki'' były rozstawiane najprostszymi, liniowymi funkcjami algebraicznymi.

Rysunek 1. Grafika ''Motyl'' to 50 linii, rozpiętych na 100 ''gwoździkach''. ''Gwoździki'' były rozstawiane najprostszymi, liniowymi funkcjami algebraicznymi.

Nasz pierwszy program (rysunek 1) rozepnie imax linii o jednakowym kolorze. Każda linia będzie rozpięta jak kolorowa nitka na niezależnej parze "gwoździków", zatem w ekran-deseczkę wbijemy 2*imax "gwoździków". Spójrzmy na prototyp algorytmu realizującego to zadanie:

int MAXX = 400, MAXY = 400;

int i, imax = 50;

int x1, y1, x2, y2;

for( i = 0; i < imax; ++ i)

{

x1 = 6 * i;

y1 = 10 * i;

x2 = MAXX - i / 3;

y2 = MAXY - i * 10;

line( x1, y1, x2, y2, BLUE);

}

Na początku definiujemy rozpiętość obrazu oraz parametry pętli, realizującej powtarzanie rozpinania linii. Współrzędne (x1,y1) i (x2,y2) wyznaczają początek i koniec linii. W każdym obrocie pętli wyliczamy ich wartości, mając nadzieję, że zespół rozpiętych na nich linii ułoży się w miły oku obraz... Potem jeszcze ustalamy kolor linii i rozpoczynamy kreślenie.

W czterech pierwszych wierszach wewnątrz pętli wyliczamy pozycje "gwoździków". Są to najważniejsze wiersze naszego programu. Tajemnica piękna albo brzydoty uzyskiwanych obrazów tkwi w matematycznej postaci formuł, które tam wpiszemy. W tym programie wykorzystaliśmy najprostsze operacje algebraiczne, ale zaraz przejdziemy do bardziej złożonych funkcji matematycznych.

Rysunek 2. Kolejnym pomysłem może być nałożenie na siebie kilku różnych, ale nadal monochromatycznych grafik. Te dwie grafiki są swoim odbiciem lustrzanym - w procedurze kreślenia linii zaledwie zamieniono między sobą niektóre współrzędne.

Rysunek 2. Kolejnym pomysłem może być nałożenie na siebie kilku różnych, ale nadal monochromatycznych grafik. Te dwie grafiki są swoim odbiciem lustrzanym - w procedurze kreślenia linii zaledwie zamieniono między sobą niektóre współrzędne.

W następnym programie (rysunek 2) wpadniemy na prosty pomysł wykreślania kilku różnych grafik jedna po drugiej i tym samym składania ich na ekranie:

int MAXX = 400, MAXY = 400;

int i, imax = 50;

int x1, y1, x2, y2;

for( i = 0; i < imax; ++ i)

{

x1 = 10 * i;

y1 = 6 * i;

x2 = MAXX - i * 10;

y2 = MAXY - i / 3;

line( x1, y1, x2, y2, BLUE);

line( x1, y2, x2, y1, YELLOW);

}

W algorytmie wykorzystujemy pętlę, w której końcówce dwukrotnie wywołujemy procedurę kreślenia linii. Te dwie linie różnią się tylko kolorem i kolejnością współrzędnych, co daje efekt lustrzanego odbicia obrazów. Jest to szczególnie proste rozwiązanie, ale nic nie stoi na przeszkodzie, żeby nałożyć na siebie całkowicie niezależne rodziny linii, generowane niezależnymi algorytmami spisanymi jeden po drugim.

Pora na kolejny krok (rysunek 3). Niech funkcje określające położenia końców linii będą dowolnie skomplikowanymi formułami matematycznymi. Większość systemów graficznych umożliwia umieszczanie początków i końców linii daleko poza ekranem - oczywiście wykreślony będzie tylko ten odcinek, który zmieści się w prostokącie ekranu. Oznacza to, że nie musimy badać zmiennych x1, ...y2, bojąc się rozpinania linii na ich wątpliwych wartościach. Grozi nam tylko to, że obrazek znajdzie się gdzieś poza ekranem i nie będzie widoczny...

Rysunek 3. Współrzędne linii zostały wyliczone za pomocą dość swobodnych iloczynów sinusów i kosinusów. Funkcje analityczne są doskonałym tworzywem, ale wymagają od artysty odrobiny wyczucia przestrzeni.

Rysunek 3. Współrzędne linii zostały wyliczone za pomocą dość swobodnych iloczynów sinusów i kosinusów. Funkcje analityczne są doskonałym tworzywem, ale wymagają od artysty odrobiny wyczucia przestrzeni.

Szczególnie godnym uwagi tworzywem są funkcje trygonometryczne - łatwo przewidywalne, ograniczone, powtarzalne. Zazwyczaj jednak dostarczane przez nie wartości musimy w jakiś sposób dopasować do pikselowej rozpiętości ekranu:

int MAXX = 400, MAXY = 400;

int i, imax = 50;

int x1, y1, x2, y2;

int x0 = MAXX / 2, y0 = MAXY / 2;

double alfa, r = MAXX / 2;

for( i = -imax; i < imax; ++ i)

{

alfa = i / 50.0;

x1 = 2 * r * sin( alfa) * cos( alfa);

y1 = r * cos( alfa);

x2 = -2 * r * sin( alfa) * cos( alfa);

y2 = -r * cos( alfa);

line( x0+x1, y0+y1, x0+x2, y0+y2, GRAY);

}

for( i = -imax; i < imax; ++ i)

{

// "trochę inny, niebieski obraz"

}

W tym programie wprowadzamy do gry parametry - przesunięcia obrazów x0 i y0 i wykorzystujemy je bezpośrednio w wywołaniach procedury linii. Dzięki nim grafika ma swoje centrum w środku okienka. Definiujemy też argument funkcji trygonometrycznych alfa i ich amplitudę r. Argument jest zależny od numeru akurat kreślonej linii. Ponadto proszę zauważyć, że mamy tu dwa niezależne algorytmy, przytoczone jeden po drugim i w efekcie otrzymujemy dwa nałożone na siebie obrazy (rysunek 3).

Rysunek 4. Równie ważnym jak współrzędne linii, a dotychczas trochę zaniedbywanym elementem tej dziedziny sztuki jest kolor.

Rysunek 4. Równie ważnym jak współrzędne linii, a dotychczas trochę zaniedbywanym elementem tej dziedziny sztuki jest kolor.

W dotychczasowych algorytmach w zasadzie zajmowaliśmy się tylko współrzędnymi końców i początków linii, zaniedbując ich kolor. W programie pokazanym na rysunku 4 wewnątrz pętli wyliczamy nie tylko cztery współrzędne, ale też trzy podstawowe amplitudy koloru R, G, B. Wielką zagadką jest dobór funkcji określających amplitudy koloru. Przed wykreśleniem linii z amplitud tych syntetyzujemy prawdziwy kolor. Oto szkic programu:

int MAXX = 400, MAXY = 400;

int x1, y1, x2, y2;

int x0 = MAXX / 2, y0 = MAXY / 2;

int R, G, B;

double alfa, alfamax = M_PI;

double dalfa = M_PI / 100.;

double r = MAXX / 2;

int kolor;

for( alfa = -alfamax;

alfa < alfamax; alfa += dalfa)

{

x1 = r * sin( alfa);

y1 = r * cos( alfa);

x2 = -r * sin( alfa);

y2 = r;

R = alfa / alfamax * 256;

G = 128 + alfa / alfamax * 128;

B = 250;

kolor = RGB( R, G, B);

line( x0+x1, y0+y1, x0+x2, y0+y2, kolor);

}

Rysunek 5. Ta grafika składa się z kwadratu, który przed wykreśleniem jest pomniejszany i obracany o odpowiedni kąt. Narożniki kolejnego kwadratu leżą na bokach poprzedniego i dzielą te boki w stałym stosunku (tutaj 9:1). Wiele takich kwadratów zostało nałożonych na siebie. Pomysł ten szczególnie łatwo jest zrealizować w języku programowania Logo.

Rysunek 5. Ta grafika składa się z kwadratu, który przed wykreśleniem jest pomniejszany i obracany o odpowiedni kąt. Narożniki kolejnego kwadratu leżą na bokach poprzedniego i dzielą te boki w stałym stosunku (tutaj 9:1). Wiele takich kwadratów zostało nałożonych na siebie. Pomysł ten szczególnie łatwo jest zrealizować w języku programowania Logo.

Każda linia ma nieco inny kolor. Przez odpowiednie uzależnienie amplitud koloru od parametru dostarczanego przez pętlę (tutaj od zmiennej alfa) otrzymujemy pożądaną zmienność barwy.

Na rysunku 5 widzimy jeszcze inną technikę pracy artystycznej - kreślenie sekwencji prostych figur, wzajemnie lekko obróconych, może przesuniętych, może przeskalowanych. Ten motyw w programowaniu jest szczególnie łatwy do realizacji w doskonałym i wszystkim znanym języku Logo. Grafika żółwia umożliwia zdefiniowanie kształtu i potem wielokrotne, automatyczne wykreślanie go z różnymi parametrami początkowymi. Na naszym krążku CD znajduje się prosta propozycja realizacji tego zagadnienia bez stosowania grafiki żółwia.

Wszystkie zaprezentowane tutaj obrazy zostały wygenerowane programami, których teksty źródłowe znajdują się na krążku CD. Programy zostały przygotowane w C++ Builderze, ale z powodzeniem można ich używać w innym środowisku programistycznym, ponieważ wykorzystujemy najprostszy, czytelny podzbiór języka C, dbając najpierw o przejrzystość algorytmu, a dopiero potem o różne szczegóły optymalizacyjne.

Tworząc własne grafiki, nie należy też za bardzo sugerować się przedstawionymi tutaj rozwiązaniami - wszak poruszamy się w obszarze sztuk artystycznych, gdzie przede wszystkim należy poszukiwać własnych rozwiązań. Czekamy na wasze oryginalne "deseczki z nićmi". Prosimy przysyłać programy, fotografie, opisy, spostrzeżenia. Wkrótce wrócimy do tego zagadnienia.


Zobacz również