Aritmetika boja i režimi preklapanja u programima za obradu fotografija
Uvod
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 (srećom) "veoma 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:
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:
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:
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:
.... 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:
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
.
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).
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:
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:
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:
Pitate se (verovatno), da li se nešto 'zapravo korisno' može postići množenjem 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.
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'.
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 ....
.... posle čega se funkcija može definisati na sledeći način:
.... a rezultat izvršavanja vidimo na slici ispod: