ASCII art
Képnézegetés parancssoron
|
Egy ASCII art programot írunk, mely bittérképes (bitmap) képeket a parancssoron ábrázol ASCII karakterek segítségével:
Az alkalmazás segítségével a fájlkezelést, a dinamikus tömböket és a struktúrákat gyakoroljuk.
A program parancssori alkalmazás, mely bekéri egy PPM formátumú bittérképes képfájl nevét, és a standard outputra rajzolja ki azt ASCII karakterekkel. A bemenet formátumának azért a PPM-et választjuk, mert az az egyik legegyszerűbben kezelhető tömörítetlen bittérképes formátum.
convert.exe batman.jpeg batman.ppmahol a
convert.exe
az imagemagick futtatható állománya, mely az imagemagick telepítése után a parancssorban elérhető.Az algoritmus egyszerű:
Beolvassuk a bitmapet a képfájlból a memóriába Kirajzoljuk a standard outputra
A beolvasáshoz természetesen pontosan ismernünk kell a PPM fájl szerkezetét. A PPM fájl egy rövid szöveges fejlécből, majd a kép pixeleit leíró bináris adatsorból áll. A szöveges fejléc pl. az alábbi lehet:
P6 640 480 255
P6
tartalmú sor alapján lehet megállapítani, hogy valóban PPM adatot tartalmazó fájllal van dolgunk.0x0A
kódú újsor karakter), ami után a pixeleket leíró bináris adatok jönnek.A PPM formátum színes képeket is képes tárolni, melyeknél egy pixelt nem írhatunk le egyetlen fényességértékkel, hanem külön meg kell adnunk annak vörös (R), zöld (G) és kék (B) színösszetevőjét. Minden egyes színösszetevőt a 0-255 tartományon mozgó világosságadattal jellemzünk, ahol a 0 a legsötétebb (fekete), 255 pedig a legvilágosabb árnyalat. Példánk bináris adatsora tehát összesen 3*640*480 bájtot tartalmaz a következő rendszerben:
+--------------+--------------+--------------+-----+----------------------+ | p[0] (R,G,B) | p[1] (R,G,B) | p[2] (R,G,B) | ... | p[640*480-1] (R,G,B) | +--------------+--------------+--------------+-----+----------------------+ 3 bájt 3 bájt
A pixelek tárolása sorfolytonos, vagyis a p[0]--p[639]
pixelek a kép legfelső sorának pixelei, a p[640]--p[1279]
pixelek a második sort adják meg és így tovább.
A bitmap tárolására az alábbi típusokat vezetjük be:
typedef
az unsigned char
beépített típust kereszteli át Byte-ra: pusztán kényelmi lépés.typedef
definiálja a Pixel struktúra típust, mely egy-egy Byte típusú adatként tartalmazza a pixel vörös, zöld és kék színösszetevőjének értékét.Image
struktúrát, mely tárolja a kép szélességét és magasságát pixelben, illetve a pixelek dinamikus tömbjének kezdőcímét.A fentiek értelmében a beolvasás algoritmusa így bontható tovább:
Megnyitjuk a képfájlt Kiolvassuk a szöveges PPM-fejlécből a méreteket memóriát foglalunk a pixeleknek egy dinamikus tömbben Beolvassuk a pixeleket a tömbbe Bezárjuk a képfájlt
A beolvasó függvényünk az image_read függvény, mely paraméterként a fájl nevét kapja, kimenete pedig egy pointer egy újonnan létrehozott Image struktúrára. Hibakezelés nélkül a függvény az alábbi:
A függvény az fscanf
függvénnyel olvassa ki a szöveges fejlécet, majd fread
-del a bináris adatokat.
fscanf
-et és az fread
-et, akkor olvasd el ezt.Vegyük észre, hogy a függvény nemcsak a pixelek tömbjét foglalja dinamikusan, hanem magát az Image struktúrát is, és nem a struktúrát adja vissza, hanem a dinamikusan foglalt struktúra címét. Ez a tervezési elv nagyon hasonlít ahhoz, ahogy a C könyvtári függvényei pl. a fájlokat kezelik. Az fopen
függvény is egy mutatót ad vissza az újonnan foglalt FILE
struktúrára, és az fclose
függvény szabadítja fel a dinamikusan foglalt adatokat. Nekünk is lesz egy image_close függvényünk, melynek feladata a képstruktúra és a benne tárolt pixeladatok felszabadítása:
További egyszerűsítésként bevezetünk egy get_pixel függvényt, mely paraméterként pointert kap az Image struktúrára, továbbá egy pixel két koordinátáját, majd visszaadja a kép adott pixelét:
Ez a függvény nagyon hasznos, mert elfedi a programozótól a sorfolytonos tárolás indexelési problémáját. A továbbiakban mindig e függvényen keresztül kérjük le a pixeladatokat, és nem kell többet sorfolytonos indexpozíciókat számolnunk.
A kirajzolás algoritmusa így bontható tovább:
A képet téglalap alakú blokkokra osztjuk, minden blokkot egyetlen ASCII karakterrel ábrázolunk MINDEN blokkra fentről lefelé, balról jobbra Meghatározzuk a blokk pixeleinek átlagos fényerejét Megfelelő fényességű ASCII karaktert írunk ki a kimenetre
Milyen fényesek az ASCII karakterek? A szóköz nyilván 0 fényességű, a @
és a #
pedig eléggé kitöltött, tehát világos. Részletesebb információ letölthető az internetről egyszerű táblázatban.
Programunkban 36 ASCII karaktert tárolunk az ASCII tömbben, fényesség szerint növekvő sorrendben. Ezek szerint a minimális fényességérték a 0 (szóköz), a maximum pedig a 35 (kukac).
A width x height
méretű kép blokkora bontásához definiáljuk, hogy a kimenetünk 80 ASCII karakter széles lesz, így egyetlen ASCII karakterhez egy W = width/80
pixel széles blokk tartozik. A blokk pixelben mért magasságát hasraütésszerűen a szélesség kétszeresére (H = 2*W) választjuk, mert karaktereink is magasabbak, mint amilyen szélesek.
A blokkok száma így
width / W
(egész osztás miatt nem biztos, hogy 80)height / H
A blokkokat bejárjuk egy dupla for ciklussal, és minden blokkon belül annak pixeleit bejárjuk egy dupla for ciklussal, melyben átlagoljuk a pixelek fényességét.
A blokkok pixeleinek átlagos fényességéhez definiálnunk kell egyetlen RGB pixel fényességét. Egy egyszerű definíció szerint átlagoljuk a pixel R G és B színkomponenseinek fényerejét. Ezt a műveletet kiemeltük a pixel_brightness függvénybe, mely paraméterként egy Pixel adatot kap, és egy Byte adatban adja meg annak fényerejét:
Figyeljük meg, hogy a függvény a Byte koordinátákat double értékekkel osztja, majd a double adatokat adja össze, az eredményt pedig ismét Byte-tá alakítja, vagyis kerekíti. Ezzel elkerüljük a túlcsordulást és az egész osztás kerekítési hibáját is.
Mivel a fenti definíciókkal egy blokk átlagos fényességét is 0 és 255 közötti értékként kapjuk meg, ezt át kell skáláznunk a 0-35 intervallumra a
int c = brightness / 256. * 36.;
művelettel, majd az ASCII tömböt a c értékkel indexelhetjük.
A kirajzolást végző függvény az image_to_ascii:
A főprogram feladata a fentiek alapján a következő:
A teljes program (rendes hibakezeléssel együtt) az ascii.c fájlban található. Letölthető innen.
Fordítás Visual Studióval (VS parancssorból)
cl ascii.c /Feascii_art.exe
gcc-vel (Windows alatt MinGW parancssorból)
gcc ascii.c -o ascii_art.exe