Aritmetika boja

Ako ste se do sada u nekom trenutku zapitali kakvi proračuni stoje iza različitih režima preklapanja u programima za obradu fotografija, obradovaćemo vas i reći da je u pitanju prilično jednostavna "matematika" (tj. aritmetika kakva se uči u nižim razredima osnovne škole).

U nastavku, pozabavićemo se time kako ("ispod haube") funkcionišu: osnovno preklapanje i Alfa kanali, kao i uklanjanje pozadine.

Konvencije pri predstavljanju boja

U računarskim sistemima, boje se tipično zapisuju preko tzv. aditivnog RGBA modela, a sam termin "aditivni", * u ovom slučaju označava model u kome se konačna boja određenog piksela formira dodavanjem: * crvene (R), zelene (G) i plave (B) boje - na crnu.

Slovo A u oznaci RGBA označava tzv. "Alfa" kanal, preko koga se definiše transparentnost piksela (više o tome u nastavku).

Najtipičniji slučaj podrazumeva da se za zapisivanje jedne od tri boje (koje čine konačnu boju nekog piksela), koristi 8 bita, odnosno, u pitanju su celobrojne vrednosti u rasponu od 0 do 255.

Ukoliko se za RGB kanale koriste 8-bitne vrednosti, Alfa kanal je takođe definisan 8-bitnom vrednošću.

Pored 8-bitnog modela, postoje i drugi modeli, sa više ili (u današnje vreme sve ređe), manje bitova, kao što je recimo 16-bitni model kakav se koristi u TIFF i PNG fajlovima (TIFF i PNG datoteke podržavaju i 8-bitni format; naravno, uz gubitak informacija o boji), a poslednjih godina, usled povećanja raspoloživih memorijskih kapaciteta, postali su popularni i pojedini 32-bitni formati (pre svih, EXR).

Međutim, kao programeri, u obavezi smo da se potrudimo da pišemo programe koji se lako mogu prilagoditi različitim zahtevima, i stoga ćemo sagledati način predstavljanja boja koji važi (uz odgovarajuće mere predostrožnosti): i u slučaju da se koriste 8-bitni fajlovi, i u slučaju da se koriste 16-bitni.

Da bi operacije koje ćemo predstaviti mogle da "funkcionišu u praksi", potrebno je koristiti tzv. "opšti model" (odnosno, "apsolutni model"), koji podrazumeva da se bilo koja od tri (RGB) komponente nekog piksela zapisuje kao decimalni broj između 0 i 1, a ne (recimo) - kao celobrojna vrednost u rasponu od 0 do 255 (što odgovara samo 8-bitnom RGB modelu).

Za primer, uzećemo da je zelena komponenta nekog piksela, u 8-bitnom modelu zapisana kao 217.

Na ekranu, navedena vrednost će biti 217, ali - u proračunima u programu, vrednost će biti: 217 / 255, odnosno 0.84765625.

Ako je potrebno da dati piksel bude prikazan na ekranu, decimalna vrednost preko koje je piksel definisan, lako se može ponovo pretvoriti u odgovarajuću celobrojnu vrednost između 0 i 255 (ili, ako zatreba u nekim drugim okolnostima, u 16-bitnu vrednost između 0 i 65536 i sl).

Ako je potrebno da određeni sloj slike (koji sadrži piksele nad kojima funkcije operišu), bude poluprovidan, Alfa vrednost takvih piksela biće 0.5 u apsolutnom modelu, dok će u 8-bitnom RGBA modelu vrednost biti 127 (0.5 * 255 = 127).

Ali, da se vratimo na primer sa osnovnim bojama (koji smo na početku uveli) ....

Vrednost 217 može se predstaviti (ili razumeti), kao:

		
217 = 255 * 217 / 255

// Ili, malo opštije ....

217 = MAX_RGB * 217 / MAX_RGB

// .... gde je MAX_RGB 255
// (što je najveća vrednost za 8-bitne kanale)
		
	
Slika 1. - Prebacivanje jedne od RGB vrednosti piksela, iz RGB modela, u apsolutni model.

U apsolutnom modelu, RGB vrednost (za određeni piksel) - koja odgovara zadatoj 8-bitnoj ili 16-bitnoj celobrojnoj RGB vrednosti - računa se preko sledećih formula:

		
Double VrednostABS;
VrednostABS = (Double) VrednostRGB / MAX_RGB;
		
	
Slika 2. - Formula za pretvaranje vrednosti iz RGB modela, u vrednost iz apsolutnog modela.

Celobrojna 8-bitna ili 16-bitna vrednost iz RGB modela - koja odgovara zadatoj vrednosti iz apsolutnog modela - može se izračunati na sledeći način:

		
Int32 VrednostRGB = (Int32) (VrednostABS * MAX_RGB);
		
	
Slika 3. - Formula za pretvaranje vrednosti iz apsolutnog modela, u vrednost iz RGB modela.

Osnovno preklapanje

Dve slike koje stoje jedna iznad druge (ili, jedna iza druge), mogu se "preklapati" na razne načine, što podrazumeva da se konačna vrednost bilo kog piksela dobija preko matematičkih formula koje operišu nad vrednostima piksela iz "donjeg" i "gornjeg" sloja.

Osnovno preklapanje podrazumeva upotrebu funkcije koja vraća vrednost piksela iz gornjeg sloja slike, što se vrlo jednostavno može zapisati na sledeći način:

		
Int32 preklapanjeGornji(Int32 donji, Int32 gornji)
{
	return gornji;
}
		
	
Slika 4. - Primitivna funkcija za "preklapanje" piksela.

.... i funkcija bi obavljala svoj zadatak adekvatno.

Međutim, šta ukoliko pri preklapanju treba zadati transparentnost (tj. prozirnost) gornjeg sloja?

U navedenom slučaju, stvari postaju komplikovanije - ali i zanimljivije!

Preklapanje sa zadavanjem transparentnosti

Osnovna funkcija (koja važi u okviru apsolutnog modela), može se implementirati na sledeći način:

		
Double preklapanjeAlfa(Double donji, Double gornji, Double alfa)
{
	return (1.0 - alfa) * donji + alfa * gornji;
}
		
	
Slika 5. - Funkcija za preklapanje dva sloja - uz korišćenje Alfa kanala (parametar alfa možemo shvatiti kao "procenat vidljivosti", i stoga: ako je gornji sloj 80% vidljiv, to znači da će donji sloj biti 20% vidljiv (100% - 80%)).

Parametri gornji i donji se zadaju kao decimalni brojevi u rasponu od 0 do 1, a isto važi i za parametar alfa (transparentnost).

Dakle: ukoliko parametar alfa ima vrednost 1 (tj. 100%), formula praktično postaje 1 * gornji, dok, u slučaju kada je alfa gornjeg kanala 0, formula praktično postaje 1 * donji.

Preklapanje slojeva slike - donji sloj
Slika 6. - Standardna fotografija koja predstavlja donji sloj slike u primerima koje ćemo koristiti u članku.
Preklapanje slojeva slike - gornji sloj
Slika 7. - Gornji kanal za prvi primer, sastoji se iz bele pozadine i crnog natpisa.
Preklapanje slojeva slike - alfa=50%
Slika 8. - Preklapanje slojeva ("alfa" gornjeg sloja je 50%)

Ostaje samo da se prepravi formula (tj. funkcija), tako da vraća RGB vrednost iz konkretnog RGB modela.

"RGB verzija" funkcije, koristi sledeće parametre: RGB vrednost za gornji i donji sloj (celobrojne vrednosti), vrednost za Alfa kanal (i dalje decimalna vrednost između 0 i 1), a takođe se predaje i maksimalna RGB vrednost za odgovarajući model (255 za 8-bitni; 65535 za 16-bitni i sl).

		
Int32 preklapanjeAlfa(Int32 donji, Int32 gornji, Double alfa, Int32 MAX_RGB)
{
	return (Int32)(MAX_RGB * ((1.0 - alfa) * donji /
	             MAX_RGB + alfa * gornji / MAX_RGB));
}
		
	
Slika 9. - Funkcija za preklapanje dva sloja (u ovom slučaju, funkcija uzima u obzir RGB vrednosti).

Kod sa slike verovatno deluje pomalo komplikovano (pri prvom susretu), i stoga ćemo analizirati pojedinačne delove.

Podsetimo se na to, da sada (za razliku od prethodne funkcije), parametri donji i gornji praktično predstavljaju celobrojne vrednosti u rasponu od 0 do MAX_RGB - 1, i stoga je prvo potrebno dve navedene vrednosti svesti na odgovarajuće vrednosti iz apsolutnog modela:

		
Donji: (1.0 - alfa) * donji / MAX_RGB; 
Gornji: alfa * gornji / MAX_RGB;
		
	
Slika 10. - Dodatno pojašnjenje: Alfa donjeg kanala računa se tako što se Alfa gornjeg kanala oduzme od 1 (pri čemu vrednost 1 možemo shvatiti i kao "100% vidljivosti").

Kada se dve vrednosti saberu, rezultat je vrednost u apsolutnom modelu - koju je potom potrebno pretvoriti u odgovarajuću vrednost iz RGB modela (što se može postići prostim množenjem sa MAX_RGB).

Pošto je krajnji rezultat i dalje decimalni broj, potrebno je još samo da se formula dopuni operatorom kastovanja (koji primećujemo na početku formule).

Preklapanje množenjem

Funkcija za množenje neke od tri RGB vrednosti određenog piksela, na dva sloja slike, može se implementirati na sledeći način:

		
Double preklapanjeMnozenjem(Double donji, Double gornji)
{
	return donji * gornji;
}
		
	
Slika 11. - Funkcija za množenje dva sloja (budući da koristimo apsolutni model, funkcija doslovno množi dve vrednosti).

Ne moramo se bojati prekoračenja, jer - u apsolutnom modelu - rezultat množenja ne može biti veći od 1.

Ukoliko pravimo funkciju koja uzima u obzir RGB vrednosti, možemo koristiti sledeći programski kod:

		
Int32 preklapanjeMnozenjem(Int32 donji, Int32 gornji, Int32 MAX_RGB)
{
	return (Int32)(MAX_RGB *
	       ((Double) donji / MAX_RGB) *
	       ((Double) gornji / MAX_RGB));
}
		
	
Slika 12. - Funkcija za množenje dva sloja (u ovom slučaju, funkcija uzima u obzir RGB vrednosti i vraća RGB vrednost).

Pitate se (verovatno), da li se nešto 'zapravo korisno' može postići "množenjem boja" iz dva sloja?

Ako se setimo priče sa početka teksta, kada smo pomenuli uklanjanje bele pozadine sa gornjeg sloja, upravo je to zahvat koji se može izvesti postupkom množenja (na besprekoran način) - pod uslovom da su pikseli iz gornjeg sloja koje treba ukloniti, skroz beli, a ostali pikseli, skroz crni.

Preklapanje slojeva slike - množenje (crno-belo)
Slika 13. - Množenjem RGB vrednosti iz pozadinskog sloja, sa RGB vrednostima iz gornjeg sloja (koji se sastoji od bele pozadine i crnog natpisa), dobijamo isti efekat kao da smo na sliku "nalepili" tekst.

Kada se RGB vrednosti donjeg piksela pomnože sa RGB vrednostima gornjeg piksela, koji je beo, rezultat ce biti - "donji piksel" (da se podsetimo: u apsolutnom modelu, bela boja ima vrednost 1, a dobro je poznato šta je rezultat množenja jedinicom). :)

Ukoliko je gornji piksel skroz crn, rezultat ce biti gornji piksel (rezultat ce biti 0, ali to je zapravo i vrednost gornjeg piksela).

Ukoliko gornji piksel nije crn, rezultat je piksel čija je vrednost proizvod RGB vrednosti dva piksela, * što ce dodatno "ušareniti" sliku.

Pozadina sa gornjeg sloja jeste uklonjena, ali (ukoliko gornja slika nije crno-bela), rezultat je .... 'diskutabilan'.

Preklapanje slojeva slike - množenje (raznobojno) - gornji sloj
Slika 14. - Ako natpis u gornjem sloju nije crn ....
Preklapanje slojeva slike - množenje (raznobojno)
Slika 15. - .... rezultat je nepredvidiv i "šaren"!

Da bismo mogli zapravo ukloniti belu pozadinu na gornjem sloju - i u situacijama kada je ostatak sloja "šaren" (tj. "ne-crn") - implementiraćemo (vrlo jednostavnu) specijalizovanu funkciju.

Preko ternarnog operatora može se ispitati da li je piksel "skroz beo":

  • ako je piksel skroz beo - funkcija vraća donji piksel
  • ako nije - funkcija vraća gornji piksel (ne mora se pozivati funkcija za množenje)

Ovoga puta prikazaćemo programski kod koji u obzir uzima sve tri RGB komponente (zarad preglednosti, do sada smo u obzir uzimali samo jednu od tri komponente).

Prvo je potrebno pripremiti strukturu koja predstavlja pojedinačni piksel ....

		
struct Piksel
{
	Int32 R, G, B;
}
		
	
Slika 16. - Struktura koja opisuje piksel, sa poljima za beleženje R, G i B vrednosti.

.... posle čega se funkcija može definisati na sledeći način:

		
Int32 uklanjanjePozadine(Piksel donji, Piksel gornji, Double MAX_RGB)
{
	Boolean uslov;
	uslov = gornji.R == MAX_RGB &&
            gornji.G == MAX_RGB &&
            gornji.B == MAX_RGB;
	
	return uslov? donji : gornji;
}
		
	
Slika 17. - Specijalizovana funkcija koja (praktično) uklanja belu pozadinu gornjeg sloja!

.... a rezultat izvršavanja vidimo na slici ispod:

Preklapanje slojeva slike - množenje - preko specijalizovane funkcije
Slika 18. - Korišćenjem specijalizovane funkcije koju smo prethodno opisali, lako dolazimo do pravog rezultata.
Napomena: Tekstovi i slike na sajtu www.skola-programiranja.rs (osim u slučajevima pojedinih fotografija, gde je drugačije navedeno) predstavljaju intelektualnu svojinu autora sajta www.skola-programiranja.rs i zabranjeno je njihovo korišćenje na drugim sajtovima i štampanim medijima, kao i bilo kakvo korišćenje u komercijalne svrhe, bez eksplicitnog odobrenja autora i Računarskog centra SystemPro. ©SystemPro d.o.o. novembar 2019.
Autor članka Nikola Vukićević Za web portal www.skola-programiranja.rs Preuzeto sa sajta www.codeblog.rs uz odobrenje autora
Podelite sa prijateljima: