Rozbij obraz na kolory HEX za pomocą PHP
[caption id=”attachment_3158″ align=”aligncenter” width=”640″] Konwersja obrazu do palety barw[/caption] [demo url=”http://mateuszmanaj.pl/demo/rozbicie-obrazu-na-kolory-hex/index.php” files=”http://mateuszmanaj.pl/application/media/download/rozbicie-obrazu-na-kolory-hex.zip”] Jeśli pracujesz z grafiką, a w szczególności tą generowaną z poziomu…
[caption id=”attachment_3158″ align=”aligncenter” width=”640″] Konwersja obrazu do palety barw[/caption]
[demo url=”http://mateuszmanaj.pl/demo/rozbicie-obrazu-na-kolory-hex/index.php” files=”http://mateuszmanaj.pl/application/media/download/rozbicie-obrazu-na-kolory-hex.zip”]
Jeśli pracujesz z grafiką, a w szczególności tą generowaną z poziomu kodu źródłowego ten artykuł może okazać się przydatny. Pokażę Ci w jak prosty sposób możesz rozdzielić dowolny obraz względem kolorów w nim występujących z określoną dokładnością.
W PHP a dokładniej w bibliotece imagemagick występuje funkcja o nazwie getImageHistogram. Funkcja ta oferuje zbliżone możliwości pracy z obrazem, lecz jaka by była z tego przyjemność gdybyśmy nie spróbowali napisać jej sami. Do dzieła!
Będziemy pracować na poniższym obrazie
Tworzymy plik o nazwie ColorUtil.php, w którym tworzymy klasę o tej samej nazwie ColorUtil. Rozpoczynamy pracę nad naszym algorytmem rozbicia obrazu na składowe kolorów.
Pierwsza rzecz o jakiej musimy pomyśleć to utworzenie metody która odczytywać będzie nasz plik i konwertować go na źródło (resource). Metodę nazywamy loadImage, ustalamy ją jako statyczną oraz dostęp do niej jako publiczny.
Dlaczego ? Tylko dlatego, aby szybciej dostać się do tej metody oraz dlatego, że specyfika tej metody (helper) pasuje na typu statycznego.
Kod klasy ColorUtil cz.1
<?php class ColorUtil { public static function loadImage($IMAGE_FILE) { ... } } ?>
Do ciała tej metody przekazujemy jeden parametr $IMAGE_FILE wskazujący na plik jaki chcemy przetworzyć. W kolejnej części wczytujemy nasz obraz
Kod klasy ColorUtil cz.2
<?php class ColorUtil { public static function loadImage($IMAGE_FILE) { $image_info = getimagesize($IMAGE_FILE); $image_type = $image_info[2]; ... } } ?>
W linii 7 pobieramy informacje o wczytywanym obrazie takie jak szerokość, wysokość czy format obrazu. Szczeółowy opis funkcji getimagesize znajduje się w dokumentacji. Po tym chcielibyśmy wiedzieć jakiego typu jest wczytywany obraz – linia 8 np. jpg czy png lub inne.
Jeśli wiemy juz z czym mamy do czynienia możemy przystąpić do „przerobienia” obrazu na typ resource. Prezentuje to poniższy kod
Kod klasy ColorUtil cz.3
<?php class ColorUtil { public static function loadImage($IMAGE_FILE) { $image_info = getimagesize($IMAGE_FILE); $image_type = $image_info[2]; if($image_type == IMAGETYPE_JPEG) { return imagecreatefromjpeg($IMAGE_FILE); } elseif($image_type == IMAGETYPE_GIF) { return imagecreatefromgif($IMAGE_FILE); } elseif($image_type == IMAGETYPE_PNG) { return imagecreatefrompng($IMAGE_FILE); } return false; } } ?>
W powyższym kodzie na podstawie wcześniej odczytanego typu pliku konwertujemy obraz za pomocą jednej z powyższych funkcji np. jeśli plikiem był JPEG użyta zostanie funkcja imagecreatefromjpeg itd.. wynik zwracamy a funkcja loadImage kończy działanie.
W następnym etapie przystępujemy do opracowania statycznej metody imageToHex o dostępie publicznym. Metoda ta przyjmować będzie 3 parametry z czego 2 będą obowiązkowe
- $IMAGE_FILE – plik obrazu do wczytania
- $COLORS_NUMBER – ilość kolorów jaką chcemy uzyskaćw rezultacie działania tej metody
- $GRANULARITY (p. nieobowiązkowy) – określa dokładność – co ile pikseli następuje odczyt koloru.
Kod klasy ColorUtil cz.4
<?php class ColorUtil { public static function loadImage($IMAGE_FILE) { ... } public static function imageToHex($IMAGE_FILE, $COLORS_NUMBER, $GRANULARITY = 5) { ... } } ?>
Pierwszy etap to ubezpieczenie się od nieznanych skutków wprowadzenia przez użytkownika dowolnych znaków innych niż np. cyrfy w ostatnim paramterze $GRANULARITY. Możemy to zrobić w następujący sposób
Kod klasy ColorUtil cz.5
<?php class ColorUtil { public static function loadImage($IMAGE_FILE) { ... } public static function imageToHex($IMAGE_FILE, $COLORS_NUMBER, $GRANULARITY = 5) { $GRANULARITY = max(1, abs((int)$GRANULARITY)); ... } } ?>
Mamy tutaj następująca sytuację
- (int)$GRANULARITY to tzw. rzutowanie (konwersja typu) do typu int. W przypadku gdy ktoś poda coś innego niż liczbę całkowitą.
- abs(…) – inaczej absolute – wartość absolutna danej liczby. Wartość liczbowa nieuwzględniająca znaku liczby – liczba zawsze dodatnia.
- max(…) – podajemy do tej funkcji tablicę lub szereg parametrów liczbowych. Z tych parametrów wybierana jest największa wartość.
W kolejnym kroku tworzyny zmienną $colors (linia 13), która będzie kontenerem na całkowity rezultat działania tej metody, sprawdzamy czy podany plik istnieje (f. file_exists(…) linia 15) oraz pobieramy wymiary pliku aby wiedzieć skąd dokąd sięga obraz (f. getimagesize(…) linia 17)
Kod klasy ColorUtil cz.6
<?php class ColorUtil { public static function loadImage($IMAGE_FILE) { ... } public static function imageToHex($IMAGE_FILE, $COLORS_NUMBER, $GRANULARITY = 5) { $GRANULARITY = max(1, abs((int)$GRANULARITY)); $colors = array(); if(!file_exists($IMAGE_FILE)) { return false; } $size = getimagesize($IMAGE_FILE); if(!$size) { return false; } ... } } ?>
Jeśli dotarliśmy do tego momentu oznacza to że podany plik w pierwszym parametrze istnieje oraz posiada szerokość i wysokość – czyli jest poprawnym plikiem obrazu.
Idąc dalej możemy teraz odnieść się do wcześniej utworzonej metody loadImage i wczytać podany plik graficzny (linia 20).
Kod klasy ColorUtil cz.7
<?php class ColorUtil { public static function loadImage($IMAGE_FILE) { ... } public static function imageToHex($IMAGE_FILE, $COLORS_NUMBER, $GRANULARITY = 5) { $GRANULARITY = max(1, abs((int)$GRANULARITY)); $colors = array(); if(!file_exists($IMAGE_FILE)) { return false; } $size = getimagesize($IMAGE_FILE); if(!$size) { return false; } $img = static::loadImage($IMAGE_FILE); if(!$img) { return false; } ... } } ?>
Zauważ, że w linii 21 sprawdzamy czy metoda loadImage nie zwraca przypadkiem false. Jeśli jednak tak się stanie przyrywamy dalsze wykonywanie metody imageToHex i zwracamy false
Kiedy i w jakich okolicznościach zostanie spełniony warunek
if(!img) {...}
?
Tylko wtedy gdy podamy w parametrze loadImage (linia 20) plik który nie jest obrazem – wtedy otrzymujemy false w rezultacie.
Teraz, gdy mamy już wczytany obraz do postaci źródła (resource) możemy przystąpić do najważniejszej części algorytmu – skanowania obrazu.
Kod klasy ColorUtil cz.8
<?php class ColorUtil { public static function loadImage($IMAGE_FILE) { ... } public static function imageToHex($IMAGE_FILE, $COLORS_NUMBER, $GRANULARITY = 5) { $GRANULARITY = max(1, abs((int)$GRANULARITY)); $colors = array(); if(!file_exists($IMAGE_FILE)) { return false; } $size = getimagesize($IMAGE_FILE); if(!$size) { return false; } $img = static::loadImage($IMAGE_FILE); if(!$img) { return false; } for($x = 0; $x < $size[0]; $x += $GRANULARITY) { for($y = 0; $y < $size[1]; $y += $GRANULARITY) { ... } } ... } } ?>
Używając dwóch pętli for umieszcoznych jedna w drugiej możemy przeskanować obraz. Pierwsza pętla linia 23 przesuwa „kursor” (wyimaginowany punkt odczytu koloru) w osi X i ustawia go za pierwszym razem w punkcie X = 0, dzięki temu zaczyna działać druga pętla linia 25. Ta z kolei przesuwa „kursor” wzdłuż osi Y
Czyli… na jedno przejście pętli X następuje przejście przez wszystkie punkty pętli Y. Uproszczony schemat działania tych pętli przedstawia poniższy obraz
Przed przejściem do dalszej części kodu wyjaśnijmy sobie szczegóły niektórych funkcji. Funkcja
- imagecolorat(…) – linia 27 pobiera indeks1 koloru z podanego obrazu w podanych punktach X i Y
- imagecolorsforindex(…) – linia 28 konwertuje wcześniej wczytany indeks1 do tablicy o kluczach „Red”, „Green”, „Blue”, „Alpha”. Czyli podstawową paletę barw razem z dodatkowym kluczem Alpha określającym stopień przezroczystości
- array_key_exists(…) – linia 36 sprawdza czy podany klucz istnieje w podanej tablicy
Po krótkim wstępie możemy opracowac kolejną porcję kodu
Kod klasy ColorUtil cz.9
<?php class ColorUtil { public static function loadImage($IMAGE_FILE) { ... } public static function imageToHex($IMAGE_FILE, $COLORS_NUMBER, $GRANULARITY = 5) { $GRANULARITY = max(1, abs((int)$GRANULARITY)); $colors = array(); if(!file_exists($IMAGE_FILE)) { return false; } $size = getimagesize($IMAGE_FILE); if(!$size) { return false; } $img = static::loadImage($IMAGE_FILE); if(!$img) { return false; } for($x = 0; $x < $size[0]; $x += $GRANULARITY) { for($y = 0; $y < $size[1]; $y += $GRANULARITY) { $thisColor = imagecolorat($img, $x, $y); $rgb = imagecolorsforindex($img, $thisColor); $red = round(round(($rgb['red'] / 0x33)) * 0x33); $green = round(round(($rgb['green'] / 0x33)) * 0x33); $blue = round(round(($rgb['blue'] / 0x33)) * 0x33); $thisRGB = sprintf('%02X%02X%02X', $red, $green, $blue); if(array_key_exists($thisRGB, $colors)) { $colors[$thisRGB]++; } else { $colors[$thisRGB] = 1; } } } ... } } ?>
Bardzo istotnym punktem są 3. linie 31, 32, 33. Trzeba tutaj odpowiedzieć w kilku kewstiach
- Czym jest liczba 0x33 ?
- Dlaczego najpierw dzielimy przez 0x33 a następnie przez tą samą liczbę mnożymy zaokrąglony wynik ?
- Czemu musimy stosować liczbę 0x33 a nie np. 432 czy 0x63 ?
Odpowiadając na powyższe pytania:
- Liczba 0x33 to inaczej liczba 51 zapisana w formacie heksadecymalnym. Zróbmy sobie krótkie przypomnienie nt. zapisu heksadecymalnego.
W systemie heksadecymalnym wyróżniamy następujące znaki:0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F
Liczby od 0 do 9 są to normalne liczby, Litery od A do F oznaczają liczby od 10 do 15
zatem sposób obliczenia jest następujący: - Chodzi tutaj wyłącznie o zaokrąglenie liczby do poprawnej wartości, który wyjaśnia punkt poniżej
- Liczba 0x33 czyli 51 to liczba która w tym przypadku powoduje konwersję koloru do tzw. web-safe color czyli koloru który wyświetli się tak samo w każdej przeglądarce. Czemu nie pomnożymy poprzez dziesiętną reprezentację liczby 0x33 ? ponieważ zgodnie ze specyfikacją systemu palety RGB mamy to opisane w ten sposób w specyfikacji – nie musimy wiedzieć ile to jest 0x33 żeby coś z tym zrobić. Nic nie stoi na przeszkodzie aby wykonać działania na dziesiętnej formie tej liczby.
Działanie wygląda następująco (dla przykładowych wartości)
$red = round(round((147 / 0x33)) * 0x33); $green = round(round((50 / 0x33)) * 0x33); $blue = round(round((38 / 0x33)) * 0x33);
Funkcja round(…) zaokrągla liczbę zmiennoprzecinkową domyślnie do dwóch miejsc znaczących. Tak otrzymane kolor Red, Green i Blue są kolorem mierzonym w danym punkie na matrycy obrazu.
Na początku wspomniałem o dokładności. Ten element określny jest przez 3 element w pętli
for(... $x += $GRANULARITY)
.
Element ten w tej pętli powoduje „skok” o konkretną wartość przy danej iteracji. Np. domyślnie określamy ten element w pętli powiedzmy jako
for(... $x++)
czyli zwiększamy za każdym przejściem pętli wartość $x o jeden. Moglibyśmy również zapisać to w formie bardziej zbliżonej to tej z naszej metody
for(... $x += 1)
Jeśli chcielibyśmy iterować w pętli co trzeci element wystarczy że zastosujemy
for(... $x += 3)
, co 7
for(... $x += 7)
itd…
„Skok” o kontretną wartość w iteracji powoduje przesunięcie „kursora” na matrycy o daną liczbę pikseli. Dla osi X w prawo, dla osi Y w dół.
Kontynuując naszą wędrówkę wzdłuż kodu w linii 34kodu cz. 9 do zmiennej
$thisRGB
wprowadzamy wygenerowane „web-safe” kolory.
W celach optymalizacji nie duplikujemy elementów tablicy które zawierają już wcześniej zdefiniowany kolor. W tym celu używamy funkcji
array_key_exists(...)
, jeśli w tablicy wyników nie ma aktualnie otrzymanego koloru, kolor ten zostaje dodany do tablicy; w przeciwnym razie nic się nie dzieje.
Warto jeszcze wspomnieć o tym w jaki sposób generowana jest tablica wyników
$colors
. Kolory RGB zapisywane są jako kluczetablice a nie jako kolejne wartości. Wartość określonego klucza określa się jako liczbę wystąpień danego koloru podczas całej iteracji. Np. jeśli kolor czerwony (FF0000) wystąpi 3 razy to w tablicy wyników pojawi się
$colors["FF0000"] = 3;
Kod klasy ColorUtil cz.10
<?php class ColorUtil { public static function loadImage($IMAGE_FILE) { ... } public static function imageToHex($IMAGE_FILE, $COLORS_NUMBER, $GRANULARITY = 5) { $GRANULARITY = max(1, abs((int)$GRANULARITY)); $colors = array(); if(!file_exists($IMAGE_FILE)) { return false; } $size = getimagesize($IMAGE_FILE); if(!$size) { return false; } $img = static::loadImage($IMAGE_FILE); if(!$img) { return false; } for($x = 0; $x < $size[0]; $x += $GRANULARITY) { for($y = 0; $y < $size[1]; $y += $GRANULARITY) { $thisColor = imagecolorat($img, $x, $y); $rgb = imagecolorsforindex($img, $thisColor); $red = round(round(($rgb['red'] / 0x33)) * 0x33); $green = round(round(($rgb['green'] / 0x33)) * 0x33); $blue = round(round(($rgb['blue'] / 0x33)) * 0x33); $thisRGB = sprintf('%02X%02X%02X', $red, $green, $blue); if(array_key_exists($thisRGB, $colors)) { $colors[$thisRGB]++; } else { $colors[$thisRGB] = 1; } } } return array_slice(array_keys($colors), 0, $COLORS_NUMBER); } } ?>
Ostatnim już krokiem jest zakończenie działania metody imageToHex poprzez zwrócenie tablicy wyników $colorsw następujący sposób
- array_keys($colors) – odczytuje wszystkie klucze podanej tablicy i zwraca tablicę kluczy – czyli tablicę w której wartości są kolorami natomiast klucze są numerowane od 0 do n
- array_slice(…) – przypomina to działanie na funkcji substr(…), która służy do wycięcia konkretnej części ciągu tekstowego lecz w porównaniu do niej nie pracujemy na ciągach znaków tylko na tablicach. Zasada działania jest taka sama. Podajemy tablicę jako pierwszy parametr potem od którego indeksu należy rozpocząć wycinanie, ostatni paramter to ilość kolejnych indeksów do wycięcia z tablicy
To już koniec tej części kodu. Teraz musimy użyć tak przygotowanego kodu i wygenerować tabelę barw.
Tworzymy plik index.php, a w nim umieszczamy kod:
Kod index.php
<?php include_once("ColorUtil.php"); $c = ColorUtil::imageToHex("color.jpg", 49); echo "<pre>"; print_r($c); echo "</pre>"; ?>
Wynik przedstawia się jako sformatowana tablica za pomoca funkcji print_r
Array ( [0] => FFFFFF [1] => CCFFFF [2] => 66CCCC [3] => CCCCCC [4] => 6699CC [5] => 3399CC [6] => 0099CC ... [45] => FF6666 [46] => FFCC33 [47] => FFCC00 [48] => FF9933 )
Po drobnych modyfikacjach, zamiast wyświetlać elementy tablicy jako ciąg znaków skonwertujemy je do formy palety kolorów 7×7
Kod index.php
<?php include_once("ColorUtil.php"); $c = ColorUtil::imageToHex("color.jpg", 49); foreach($c as $k => $color) { if($k % 7 == 0) echo "<br>"; echo "<div style='background-color: #{$color}; width: 50px; height: 50px; display:inline-block;'></div>"; } ?>
Oczywiście zamiast pliku color.jpg podajemy swój plik który wgraliśmy do tej samej lokalizacji co plik index.php
if($k % 7 == 0) echo "<br>";
oznacza że za każdym 7. elementem wstawiana jest nowa linia (<br>). W efekcie uzyskujemy następujący obraz
Porównując paletę z pierwowzorem obrazu
Serdecznie zapraszam do komentowania i oceniania na moim blogu: http://mateuszmanaj.pl/