Wreszcie będzie coś dla prorgamistów, po całej serii postów o różnych linuksach. Doszedłem do wniosku, że za dużo już pisałem o systemach i wypadałoby wreszcie wziąć się za programowanie. Z pomocą przyszła mi pani prowadząca zajęcia ze Wstępu do Przetwarzania Sygnałów i Obrazów. Moim zadaniem było zapoznanie się z techniką generowania obrazu metodą półtonów i napisania odpowiedniego programu na tę okoliczność.
Obraz ma być w skali szarości ale system powinien zadziałać również dla normalnych obrazków RGB jeśli zastosujemy sposób do każdego kanału osobno. Efekt działania takiego algorytmu wygląda następująco:

Z lewej obraz oryginalny, z prawej przetworzony
Cały pomysł na zamianę polega na tym, aby podzielić obraz źródłowy na małe kwadraty pikseli (u mnie 3×3 piksele), a następnie dla każdego z tych pikseli wybrać czy ma on być zamalowany na czarno czy na biało. W jaki sposób określić, który piksel ma być jaki?
Na stronie http://wwwhome.cs.utwente.nl/~schooten/graphics/ opisany jest sposób dla bloków 2×2 piksele oraz skali szarości w przedziale od 0 do 1. Autor sugeruje stworzenie macierzy 2×2 zawierającej… hmm… odchylenie? Zachęcam do zapoznania się z jego artykułem gdyż chciałbym jedynie przedstawić ten sposób dla bloku 3×3 piksele oraz skali szarości od 0 do 255.
Jeśli mamy blok 3×3 piksele to jeśli zamalujemy wszystkie na biało to oczywiście otrzymamy kolor biały (tu akurat łatwo wszystkich przekonać
). Jeśli zamalujemy wszystkie piksele na czarno to będziemy mieli kolor czarny. Jeśli zamalujemy jeden z pikseli na czarno, a resztę na biało to patrząc z daleka będzie nam się wydawało, że blok jest troszeczkę szary. W ten sposób (zamalowując kolejne piksele na czarno) możemy stworzyć 10 poziomów szarości dla bloku 3×3. Poniżej przedstawiam przykładową reprezentacje poziomów 1, 5 oraz 9:

Poziomy 1, 5 i 9
Teraz należy stworzyć tablicę odchylenia dla pikseli z danego bloku. Aby to zrobić trzeba najpierw rozłożyć naszą skalę szarości (0-255) na 9 przejść pomiędzy poziomami. Dla N poziomów mamy N-1 przejść – jeśli ktoś ma problem ze zrozumieniem dlaczego to niech narysuje sobie 10 słupków od ogrodzenia i policzy ile jest potrzebnych części płotu pomiędzy słupkami
– te części to przejścia. Wykonajmy dzielenie: 255/9 = 28,(3). Ten wynik nie jest zbyt piękny, ale wystarczy, że zaokrąglimy sobie do dwóch miejsc po przecinku. Otrzymaliśmy zatem 28,33.
Teraz jest najciekawiej. Jeśli narysujemy sobie wszystkie możliwe poziomy szarości dla bloku 3×3 i odrzucimy skrajne (tzn. cały czarny i cały biały) to łatwo będzie stworzyć tablicę odchyleń. Patrzymy zatem na poziomy 1-8. Tablica odchyleń oczywiście też musi być wielkości 3×3. Teraz patrzymy na pierwszy piksel po lewej na górze (poziom 1). Co w tym miejscu wpisać w tablice odchyleń? Wystarczy policzyć na ilu poziomach ten piksel jest czarny (oczywiście patrząc wyłącznie na poziomy pośrednie [1-8]) . W przypadku lewego górnego – jest on czarny na wszystkich 8 poziomach zatem w tablicę odchyleń wpisujemy dla niego wartość ~(8*28.33). Warto ją zaokrąglić
Podobnie postępujemy dla kolejnych pikseli. W końcu otrzymujemy macierz postaci:

Macierz odchyleń
Mając taką macierz wystarczy już tylko do każdego piksela dodać odpowiadającą mu wartość z powyższej macierzy i później sprawdzić czy ma on wartość większą niż 255. Jeśli tak to ustawiamy ten piksel na kolor biały (255) jeśli nie to ustawiamy go na kolor czarny (0). W ten sposób otrzymaliśmy dość ciekawy obraz oraz zmniejszyliśmy ilość potrzebnych kolorów do dwóch (biały i czarny) zachowując przy tym 10 poziomów szarości.
Nie jest to może najpiękniejszy obraz ale efekt powinien być dobrze znany starszym graczom, którzy w dzieciństwie na takie cuda się napatrzyli
Na koniec podam jeszcze ostateczny algorytm w pseudokodzie w C++:
int bias[3][3] = {
{ 227, 198, 170 },
{ 142, 113, 85 },
{ 57 , 28 , 0 }
};
int value;
for(int y=0; y < in.height(); y++) {
for(int x=0; x < in.width(); x++) {
value = in.pixel(x,y) + bias[y%3][x%3];
if(value > 255) value = 255;
else value = 0;
out.setPixel(x, y, value);
}
}
Podczas pracy algorytmu dość istotne jest w jakim formacie jest zapisany obraz. Powyżej założyłem, że do zapisu każdego piksela wykorzystane zostało 8 bitów informujących o skali wartości w skali szarości.