Régi fa abakusz fekete háttéren

Episode I

Alice és Bob

3. fejezet

Alice és Bob számolni tanul

Az előző fejezetben láttuk, hogy a számítógépek megjelenésével az Alice és Bob közötti titkos kommunikáció a továbbiakban nem pusztán emberi nyelven írt bizalmas szövegek küldözgetésére korlátozódik. Ezért elkezdtük felépíteni az információátviteli rendszerek absztrakt modelljét annak érdekében, hogy megfelelően általánosan tudjuk tárgyalni ezt a kérdést a továbbiak során. Ennek első állomásaként eddig két összetevővel ismerkedtünk meg: az egyik volt az információforrás, a másik pedig a forráskódoló.

De vajon hogyan történik a digitális információ reprezentációja az informatikai rendszerekben? Mik azok a számrendszerek? Hogyan kell elképzelni a számítógépek memóriáját és mi van benne? Hogyan épül fel egy képet tartalmazó file és mit tartalmaz? Ebben a fejezetben erről lesz szó...

Az előző fejezetben megismert digitális információforrás felfogható egy olyan absztrakt objektumnak, amely egy véges (például elemű) halmazból vett elemek sorozatát produkálja. Mivel az elemű halmaz véges, ezért ennek elemeihez egyértelműen hozzárendelhetjük a , , ..., egész számokat. Egy szöveges információforrás esetén ezek lehetnek a különböző karakterekhez rendelt egész számok például az ASCII kódtábla alapján (lásd a 2.2. szakaszt), egy hangforrás vagy bármilyen más fizikai folyamat esetén pedig az őt leíró analóg jel digitalizált változatát alkotó számok (lásd a 2.5. szakaszt). Látható, hogy matematikai szempontból a közölt információ konkrét tartalmának nincs jelentősége. Ezért azt csupán egy egész számokból álló sorozatnak fogjuk tekinteni. Ráadásul másnak tekinteni nem is lenne értelme, mivel egy informatikai rendszer a vizsgálatunk tárgya, amely számokon kívül semmi mást nem tud értelmezni és feldolgozni.

Ez a számsorozat azután a forráskódoló bemenetére kerül. Ennek feladata ebből egy olyan bitsorozat (az adat) előállítása, amely már átküldhető a kommunikációs csatornán. A forráskódolás témakörének vizsgálata messzire vezetne, ezért itt ezt nem fogjuk részletezni. Azt azonban fontos tudnunk, hogy a számokat hogyan lehet leírni bitsorozatokkal, ezért először a különböző számrendszerekről ejtünk néhány szót.

Számrendszerek

A mindenkinek ismerős tízes (vagy más néven decimális) számrendszer kialakulásához mindenképpen szükség volt az úgynevezett helyiértékes írásmódra, amelyet először az ókori Mezopotámiában vezettek be. A történelmi áttekintés mellőzésével egy példán keresztül mutatjuk be ennek lényegét. Tegyük fel, hogy meg kell számlálnunk bizonyos dolgokat, és kavicsokkal szeretnénk jelölni a számlálás eredményét. Az egyik megoldás, hogy a számlálás során dobáljuk le a kavicsokat a homokba, így a végeredmény a homokban lévő kavicsok száma lesz. Ennek hátránya, hogy egyrészt sok tárgy megszámlálásához rengeteg kavicsra van szükségünk, másrészt pedig a végeredmény egy rakás kavics lesz, amivel nem vagyunk sokkal előrébb. Nem könnyű például megmondani két közel azonos méretű kavicshalomról, hogy melyik a több.

Egy ennél jóval hatékonyabb eljárás a következő. Rajzoljunk egymás mellé köröket a homokba, és határozzuk meg, hogy maximum hány kavicsot engedünk meg egy körön belül. Legyen ez például most . Ezután kezdjük el a jobb szélső körbe dobálni a kavicsokat. Ezt mindaddig folytathatjuk, amíg el nem értük a maximális kavicsot a jobb szélső körben. Ekkor a következő kavicsot már az eggyel balra lévő körbe dobjuk, az első kört pedig kiürítjük. Ezt láthatjuk a 3.1. ábrán.

Számolás kavicsokkal
3.1. ábra: Számolás kavicsokkal

Ezután ismét dobálhatunk az első körbe. Amikor az újra megtelt, a következő kavicsot megint a második körbe dobjuk, és a 3.2. ábrán látható módon ismét kiürítjük az első kört.

Számolás kavicsokkal
3.2. ábra: Számolás kavicsokkal (folytatás)

Ezt folytatva egy idő után elérkezünk egy olyan pontra, amikor megtelik az első és a második kör is. Ekkor a következő kavicsot már a harmadik körbe dobjuk, és kiürítjük mind az első, mind pedig a második kört, ahogyan az a a 3.3. ábrán látható.

Számolás kavicsokkal
3.3. ábra: Számolás kavicsokkal (folytatás)

És így tovább, ezt az eljárást a végtelenségig folytathatjuk. Vegyük észre, hogy így nagy számok esetén lényegesen kevesebb kavicsra van szükségünk ahhoz képest, mint amekkora mennyiséget jelölünk velük. A módszer hatékonysága abban rejlik, hogy a kavicsoknak különböző értékei vannak attól függően, hogy melyik körben helyezkednek el. Az első körben lévő kavicsoknak az értéke, és minden további körben az értékük megháromszorozódik. A második körben egy kavics értéke már , a harmadik körben már , a negyedik körben és így tovább, a -adik körben egy-egy kavics értéke.

A 3.4. ábrán például a egész szám kavicsokkal történő reprezentációját láthatjuk, és ehhez mindössze kavicsra van szükségünk. Az egyes körök alatt feltüntettük, hogy mekkora az értéke egy-egy kavicsnak az adott körben.

A 2019-es szám kavicsokkal jelölve
3.4. ábra: A 2019-es szám kavicsokkal jelölve

Gratulálhatunk magunknak, ugyanis lényegében feltaláltuk a helyiértékes számábrázolást. Konkrétan a fenti példában a hármas számrendszert. A dolgot továbbfejleszthetjük azzal, ha különböző szimbólumokat találunk ki egy-egy kör lehetséges tartalmainak leírására. Jelen esetben féle szimbólumra van szükségünk, mivel egy kör tartalma féle lehet: üres, egy kavics, két kavics. Legyen ez a féle szimbólum például rendre a 0, az 1 és a 2. Ekkor kavicsok helyett a -es szám leírására használhatjuk például a 2202210 szimbólumsorozatot.

Ehhez hasonlóan leírhatjuk a számokat tetszőleges -alapú számrendszerben, ha . Egy -alapú számrendszer semmiben sem különbözik a fenti kavicsos példától. Ekkor az egyes körökben elhelyezhető kavicsok maximális száma lesz, egy adott körben lévő kavicsok értéke pedig megfelelő hatványa lesz a kör pozíciójának (amit helyiértéknek nevezünk) függvényében. A jobb szélső kör helyiértéke , míg a további körök helyiértékei rendre , , , és így tovább. Egy adott kör helyiértéke mindig a tőle jobbra lévő kör helyiértékének -szerese.

A megszokott tízes számrendszerben ezek a helyiértékek például: (azaz ), (azaz ), (azaz ), stb. A maja, valamint a pre-kolumbiai és közép-amerikai civilizációk 20-as számrendszert használtak. Ennek eredete feltehetőleg összefügg az emberek kéz- és lábujjainak számával. A 60-as számrendszert a sumér és az azt követő mezopotámiai kultúrák használták. Ez a ma használt időmérő rendszerben is visszaköszön: egy órát 60 percre, illetve 1 percet 60 másodpercre osztunk. A 12-es számrendszer maradványai is fellelhetők például abban, hogy az év 12 hónapra oszlik. Csaknem minden nyelvben külön szó van a 12 dologból álló csoportra, például a magyar "tucat", az angol "dozen", a német "das Dutzend", az olasz "dozzina", az orosz "djuzsina" stb.

A bináris számrendszer

Most nézzük meg, hogy esetén (ezt bináris vagy 2-es számrendszernek nevezzük) hogyan tudjuk leírni az előző példában szereplő -es számot. A bináris számrendszer helyiértékei rendre , , , , , és így tovább. A helyiértékeket reprezentáló körök tartalma ebben az esetben kétféle lehet: vagy nincs kavics az adott körben, vagy pedig van. Ezt a két esetet rendre a 0 és az 1 szimbólumokkal fogjuk jelölni a számábrázolásban. Ezután megkeressük azt a legnagyobb helyiértéket, amely még éppen nem nagyobb az ábrázolni kívánt számunknál, azaz -nél. Ez a lesz, így az ennek megfelelő körbe annyi kavicsot helyezünk, amennyinek az összértéke még éppen nem haladja meg a -et. Ez jelen esetben a maximális darab kavics lesz, így leírjuk az ennek megfelelő 1 szimbólumot, és az elhelyezett kavics értékét () levonjuk a -ből, eredményül -öt kaptunk. Ezután ezzel az -tel megyünk tovább az eggyel alacsonyabb helyiértékre, ez a lesz. Ebbe a körbe megint annyi kavicsot helyezünk, hogy ezek összértéke ne legyen nagyobb -nél. Ez megint a maximális darab kavics lesz, tehát leírjuk az ennek megfelelő 1 szimbólumot is, és az elhelyezett kavics értékét () levonjuk az -ből. Így a számunk ábrázolása eddig az 11 szimbólumsorozat, a maradék pedig .

Ezt az eljárást a 3.5. ábrán látható módon folytatjuk egészen a legalacsonyabb helyiértékig. Végeredményül az 11111100011 szimbólumsorozatot kapjuk, amely tehát a -es szám bináris számrendszerbeli ábrázolása.

A 2019-es szám bináris számrendszerben
3.5. ábra: A 2019-es szám bináris számrendszerben

A most bemutatott bináris számrendszer rendkívül fontos az informatikai rendszerek szempontjából. A 0 és 1 szimbólumokat ugyanis könnyű elektronikus eszközökkel kezelni. Például folyik-e áram egy vezetékben vagy nem? Van-e töltés egy kondenzátorban vagy nincs? Van-e lyuk égetve egy DVD lemezre vagy nincs? Egy bináris szám számjegyeit bit-eknek nevezzük, amely kifejezés az angol "binary digit" (azaz bináris számjegy) rövidített változata. Megfelelő áramkörökkel bináris számokon műveleteket tudunk végezni az őket reprezentáló fizikai jelek manipulálása által, méghozzá veszettül gyorsan. Például 64-bites egész számok szorzásának időtartama egy átlagos processzor esetén a nanoszekundumos nagyságrendbe esik. Ez egyetlen másodperc alatt nagyságrendileg 1 milliárd szorzás elvégzését jelenti. A bináris számok szorzása, összeadása, és egyéb műveletek végzése helyiértékenként történik nagyon hasonlóan ahhoz, mint ahogyan az írásban számolást tanultuk általános iskolában, tízes számrendszerben.

Az alábbi példában ugyanazon két szám írásbeli összeadásának folyamata látható bináris és tízes számrendszerben:

A hexadecimális számrendszer

Annak érdekében, hogy ne kelljen állandóan olyan sok bináris számjegyet leírnunk, bevezethetünk egy praktikus rövidítést. Ha a biteket képzeletben négyesével csoportosítjuk, és ezeket a csoportokat egy-egy szimbólummal helyettesítjük, akkor egy sokkal rövidebb írásmódot kapunk. Ez az ábrázolás épp a 16-os, vagy más néven hexadecimális számrendszernek felel meg, mivel bináris helyiértéken épp féle bitsorozat írható le. Méghozzá pontosan azok, amelyek megfelelnének a , , , ..., darab kavics elhelyezésének az adott csoportnak megfelelő hexadecimális helyiértéken.

A hexadecimális számjegyek jelöléséhez különböző szimbólumra van szükségünk. Ezért a szokásos 0, 1, 2, …, 9 szimbólumok mellé be kell vezetnünk darab további szimbólumot is: A, B, C, D, E és F (vagy ezek kisbetűs változatai), amelyek rendre a , , , , és kavics elhelyezését jelentik. Amikor egy hexadecimális számot leírunk, akkor általában a számjegyek felsorolása elé a "0x" prefixumot szoktuk írni a hexadecimális számrendszer jelölésére.

Az alábbi példában a -es szám bináris leírásának hexadecimális rövidítése látható:

Ehhez hasonlóan a 2.2. szakaszban ismertetett ASCII kódtábla szerint kódolt Bob üzenet 24 bites kódját a 0x42, 0x6F, 0x62 hexadecimális számsorozattal is leírhatjuk. Ne feledjük azonban, hogy ez csupán a bitsorozatok leírásának egy rövidítése. A digitális áramkörök ettől függetlenül természetesen továbbra is kettes számrendszerben számolnak. Most vizsgáljuk meg, hogyan mi van egy informatikai eszköz memóriájában vagy például egy bináris adatokat tartalmazó file-ban.

A memória felépítése

Egy informatikai rendszerben futó folyamat bemenő adatai, valamint a folyamat által végrehajtandó program utasításai a futás megkezdése előtt egy memóriának nevezett tárolóba kerülnek. Erre azért van szükség, mert a memóriát sokkal gyorsabban lehet írni és olvasni, mint a hosszútávú tárolásra szolgáló úgynevezett háttértárat (ami a legtöbbször a merevlemez). A folyamat a memóriát használja munkaterületként. Innen olvassa a végrehajtandó utasításokat és a bemenetet, itt tárolja a részeredményeket, valamint a kimenet is itt fog megjelenni, amelyet aztán vissza lehet írni a háttértárra, ha a hosszabb távú tárolás szükséges.

A tárolás/adattovábbítás szempontjából a byte (azaz bitek 8-as csoportja) tekinthető atomi egységnek vagy legalsóbb szintnek. A memória ennek megfelelően kis rekeszekből áll, amelyek mindegyike egy-egy byte-ot tárol. A rekeszek -tól kezdődően sorszámozva vannak, egy ilyen sorszámot az adott rekesz memóriacímének nevezünk. A folyamat ezekkel a címekkel hivatkozik a memóriában lévő rekeszekre, amikor írni vagy olvasni akarja őket.

Ez a memóriamodell látható az alábbi táblázatban:

Egy informatikai rendszerben azonban egynél több folyamat is futhat párhuzamosan. Ezek mindegyike ugyanazt a fizikai memóriát használja munkaterületként, ezért biztosítani kell, hogy mindegyik folyamat csak a saját adataihoz férjen hozzá. Nem lenne jó például, ha az egyik folyamat valamely részeredményét egy párhuzamosan futó másik folyamat véletlenül felülírhatná. Ennek biztosítása az operációs rendszer feladata, amely úgy mutatja a memóriát az egyes folyamatok számára, mintha mindegyik kizárólagosan rendelkezne a teljes címtartománnyal. Ezt a mechanizmust virtuális memóriakezelésnek nevezzük. A folyamatok tehát virtuális címeket használnak az adataik eléréséhez, és az operációs rendszer képezi le ezeket a valódi, fizikai címekre. Erről bővebben itt lehet olvasni. Számunkra azonban most csak annyi a fontos, hogy egy adott folyamat szempontjából a memória a fenti táblázatban látható módon néz ki.

Hasonló mondható el a háttértáron lévő file-ok tartalmáról is, valamint a hálózati kommunikáció során továbbított adatok is lényegében tekinthetők byte-ok sorozatának. A továbbiakban tehát általánosságban byte-sorozatokról lesz szó, függetlenül attól, hogy ezek a memóriában vagy egy file-ban lévő, netán valamilyen kommunikációs csatornán továbbított adatokból származnak.

Előjel nélküli egészek

Az, hogy egy byte-sorozatban az egyes byte-ok mit jelentenek, az adott alkalmazástól függ, vagy valamilyen szabványban van leírva. Ennek a fejezetnek a végén mutatunk erre egy példát, amikoris megvizsgáljuk egy képet leíró file szerkezetét. Az azonban általánosságban elmondható, hogy van néhány alap adattípus, ami a legtöbb alkalmazásban előfordul.

A legegyszerűbb példa az úgynevezett előjel nélküli egész számok (unsigned integer) ábrázolása. Ez tipikusan 1, 2, 4 vagy 8 byte-on (azaz rendre 8, 16, 32 vagy 64 biten) szokott történni attól függően, hogy milyen értéktartományra van szükségünk, és mennyi memóriát szánunk egy-egy érték tárolására. Ezek azonban csak a "beépített" típusok. Ha ennél nagyobb számokkal akarunk dolgozni, annak természetesen nincs elvi akadálya. Viszont ekkor ezek aritmetikáját (az egyes műveletek végrehajtásának módját) magunknak kell leprogramozni. A legtöbb alkalmazáshoz azonban a 64 bit (azaz 8 byte) bőven elegendő, hiszen ennyi helyiértékkel különböző egész számot tudunk leírni. Az előjel nélküli egész számokat leíró byte-ok sorrendje általában kétféle szokott lenni. Little endiannak nevezzük azt a sorrendet, amikor a kisebb helyiértékű byte-ok vannak elöl, és ezt követik az egyre nagyobb helyiértékűek. Az ezzel ellentétes sorrendet big endiannak nevezzük.

Tekintsük például a -ös számot, és nézzük meg, hogy ez 32-bites little endian byte-sorrendű előjel nélküli egészként ábrázolva hogyan nézne ki egy byte-sorozatban. A -ös szám bináris alakja 32 biten 00000000000000000100011110111101. Ezt byte-okra osztjuk és felsoroljuk ezeket a byte-okat a legkisebb helyiértéktől kezdve. Ekkor a következő byte-sorozatot kapjuk: 0xBD, 0x47, 0x00, 0x00. Ugyanezt a számot 64 biten ábrázolva további 4 darab 0x00 byte is szerepelne a fenti 4 byte után.

Az alábbi táblázatban a -ös számot leíró byte-ok láthatók 32-bites little endian ábrázolásban az -edik memóriacímtől kezdve:

Ugyanez a szám 32-bites big endian ábrázolásban:

Úgy tűnik, nincs jelentős előnye egyik ábrázolási módnak sem a másikkal szemben, és használatuk az adott számítógép-architektúrától függ. A legtöbb szabvány mindenesetre a little endian módot írja elő. Ennek oka talán abban keresendő, hogy az ezt használó úgynevezett Intel x64 processzor-architektúra a legelterjedtebb.

Előjeles egészek

Természetesen előjeles egész számokat (signed integer) is tárolhatunk. Itt is választhatunk a 8, 16, 32 vagy 64 bites ábrázolásmódok közül. Az ilyen számok ábrázolása a következőképpen történik: amennyiben az ábrázolni kívánt szám pozitív, azt ugyanúgy ábrázoljuk, mint az előjel nélküli esetben. Ha viszont a szám negatív, akkor leírjuk őt binárisan úgy, mintha pozitív lenne, majd minden így kapott bitet az ellentettjére cserélünk (ahol 0 volt, oda 1 kerül, és viszont, ezt invertálásnak nevezzük), végül az eredményhez hozzáadunk -et. Az így kapott bitsorozatot tároljuk le ugyanúgy byte-onként, mint az előjel nélküli esetben.

Tekintsük például a -ös számot, vagyis az előző példa negatív változatát. Ennek pozitív változata 32 biten ábrázolva a 00000000000000000100011110111101 bitsorozat. Ezt a bitsorozatot invertáljuk, és az eredményhez hozzáadunk -et. A kapott bitsorozat: 11111111111111111011100001000011. Ezt little endian sorrendben letárolva a kapott byte-ok: 0x43, 0xB8, 0xFF, 0xFF. Ugyanezt a számot 64 biten ábrázolva további 4 darab 0xFF byte is szerepelne a fenti 4 byte után.

Az alábbi táblázatban a -ös számot leíró byte-ok láthatók 32-bites little endian ábrázolásban az -edik memóriacímtől kezdve:

Ugyanez a szám 32-bites big endian ábrázolásban:

Ezt kettes komplemens számábrázolásnak nevezzük. Ilyenkor tehát a legnagyobb helyiértékű bitet előjelként használjuk: ha ez a bit 0, akkor az ábrázolt számot pozitívnak, ha pedig 1, akkor negatívnak értelmezzük. Az invertálásnak és az eredmény -gyel való megnövelésének praktikussági okai vannak. Egyrészt, ha egy számnak képezzük a kettes komplemensét, majd az eredménynek ismét képezzük a kettes komplemensét, akkor az eredeti számot kapjuk vissza (épp úgy, mintha kétszer vennénk egy szám ellentettjét). Ezen kívül a digitális összeadó áramköröket kivonásra is használhatjuk változtatás nélkül. Ha ugyanis egy bináris számhoz hozzáadjuk egy másik szám kettes komplemensét, akkor épp olyan eredményt kapunk, mintha kivonást végeztünk volna. Ráadásul, ha egy kisebb számból vonunk ki egy nagyobbat ilyen módon, akkor épp az így keletkező negatív eredményt kapjuk szintén kettes komplemens ábrázolásban. Ezek általános bizonyításától most eltekintünk, de érdemes kipróbálni papíron, hogy tényleg működik.

Itt csak megemlítjük, hogy természetesen nem csak egész, hanem tört számokat is tudunk ábrázolni (és rajtuk műveleteket végezni) a számot leíró bitek megfelelő értelmezésével. Ennek leggyakoribb formája az úgynevezett lebegőpontos számábrázolás. Ennek nagyjából az a lényege, hogy a számot leíró bitek egyik csoportja a "hasznos" számjegyeket írja le, a bitek egy másik csoportja pedig azt, hogy ebben a számjegysorozatban hol helyezkedik el a tizedesvessző. Ennek részleteire azonban itt nem térünk ki.

Egy kép bináris leírása

Most egy példán keresztül bemutatjuk, hogyan kell a fentieket elképzelni a gyakorlatban. A 2.2. szakaszban megpróbáltunk megnyitni egy MP3 file-t a jól ismert Notepad alkalmazással, és azt tapasztaltuk, hogy egy látszólag értelmetlen adathalmaz van benne. Most egy hasonló példát fogunk megnézni, azonban vizsgálatunk tárgya az egyszerűség kedvéért MP3 helyett egy BMP típusú kép lesz.

A 3.6. ábrán egy tesztképet láthatunk kinagyítva, amely mindössze 2x2 képpontból áll, és amelynek a bal-felső és a jobb-alsó képpontja fekete, míg a bal-alsó és a jobb-felső képpontja fehér színű.

2x2-es tesztkép nagyítva
3.6. ábra: 2x2-es tesztkép nagyítva

Most a fehér képpontokat szürkékre fogjuk cserélni, ám ezt nem a képszerkesztővel, hanem a file-ban szereplő megfelelő byte-ok átírásával fogjuk megtenni. Ha megnyitnánk a képfile-t Notepad-ban, az MP3-hoz hasonlóan megint egy látszólag értelmetlen ASCII karaktersorozatot látnánk. Léteznek azonban olyan szövegszerkesztők, amelyek képesek megjeleníteni a file-ban lévő byte-okat hexadecimális alakban is.

Egy ilyen szövegszerkesztővel megnyitva a file-t láthatóvá válik az a 70 darab byte, amely ezt az egyszerű 2x2 pixeles képet leírja. A byte-ok a 3.7. ábrán látható sorrendben sorfolytonosan követik egymást (balról jobbra olvasva).

Tesztkép byte-jai
3.7. ábra: Tesztkép byte-jai

Azt, hogy ebből a 70 byte-ból melyik mit jelent, a BMP szabvány írja le. A részletekbe most nem megyünk bele, a számunkra ebből most annyi fontos, hogy a file alapvetően két részből áll. Az első rész az úgynevezett fejléc, amely információkat tartalmaz a képpel kapcsolatban, míg a második rész tartalmazza a konkrét képpont adatokat. Ez utóbbi a fenti ábrán a feketével bekeretezett rész.

Egy BMP file fejléce minden esetben a B és M karakterek ASCII-kódjával, azaz a 0x42 és 0x4D értékű byte-okkal kezdődik (ez az ábrán is látszik). Ez egy előzetes ellenőrzést tesz lehetővé az alkalmazás számára, hogy valóban egy BMP formátumú képpel van-e dolga. A fejléc továbbá – többek között – tartalmazza, hogy hány képpont a kép szélessége és magassága, hogy milyen módon van leírva egy-egy képpont színe a képpont adatoknál, illetve hogy hanyadik byte-tól kezdődnek konkrétan ezek a képpont adatok. Ez utóbbi információt a fejlécben a 10. sorszámú pozíciótól (a számozás 0-tól indul) kezdődő 4 byte tartalmazza (0x36, 0x00, 0x00, 0x00). Ezeket a szabvány szerint 32-bites little endian byte-sorrendű előjel nélküli egészként kell értelmezni. Ennek értéke tehát tízes számrendszerben . Valóban, a feketével bekeretezett pixel adatok kezdete épp az 54. sorszámú byte. A fejléc tartalmát a továbbiakban nem részletezzük, ehelyett ugorjunk a bekeretezett részhez, azaz a képpont adatokhoz.

Az egyes képpontok ebben a BMP file-ban 3-3 byte-tal vannak leírva, amelyek rendre az adott képpont színének kék, zöld és vörös színösszetevőit adják meg. A színösszetevők intenzitását a megfelelő byte értéke határozza meg: a 0x00 érték az adott színösszetevő teljes hiányát, míg a 0xFF érték a maximális intenzitását jelenti. Ezeket RGB-színkódoknak nevezik, utalva a "red", "green" és "blue" szavak kezdőbetűire.

A kép soronként lentről felfelé tárolódik, vagyis a képpont adatok a legalsó sorral kezdődnek. Az első 3 byte (0xFF, 0xFF, 0xFF) tárolja tehát a bal alsó pixel RGB-színkódját, amely a fehér színnek felel meg. Ha megnézzük a képet, a bal alsó pixel valóban fehér. A következő 3 byte (0x00, 0x00, 0x00) a következő, azaz a jobb alsó pixel színét tárolja, amely valóban a fekete. Ezzel vége az alsó sornak, azonban van itt még 2 darab 0x00 értékű byte, a szabvány szerint ugyanis minden sort szükség szerint 0 bitekkel kell feltölteni oly módon, hogy az egyes sorokhoz -gyel osztható számú byte tartozzék. Ez programozástechnikailag megkönnyíti az ilyen file-ok kezelését, ennek részleteire most nem térünk ki.

A következő 3 byte (0x00, 0x00, 0x00) már a következő sor első pixelét, azaz a bal felső fekete pixelt tárolja. Ezután pedig az utolsó, jobb felső fehér pixel RGB-kódja következik (0xFF, 0xFF, 0xFF). Végül ismét 2 darab 0x00 értékű kitöltő byte következik az előző sorhoz hasonlóan. Ezzel vége a pixel adatoknak, és egyben a BMP file-nak is.

Most módosítani fogjuk a bal alsó és a jobb felső pixel színét leíró byte-okat. Azt szeretnénk, hogy ezek szürkék legyenek. A szürke színben a fehérhez hasonlóan mindhárom színösszetevő szerepel, csak éppen nem a maximális (0xFF) intenzitással. Egy közepes intenzitású szürke színt kapunk, ha például mindhárom összetevőt 0x80-ra állítjuk. A 3.8. ábra mutatja a módosított byte-okat feketével bekeretezve.

Tesztkép byte-jai módosítva
3.8. ábra: Tesztkép byte-jai módosítva

Ha ezek után megnyitjuk a módosított file-unkat és kellőképpen ránagyítunk a képre, akkor láthatjuk, hogy a bal-alsó és a jobb-felső képpont színe a várakozásoknak megfelelően fehérről szürkére módosult. A 3.9. ábrán ez látható kinagyítva.

Módosított tesztkép
3.9. ábra: Módosított tesztkép

Láthatjuk tehát, hogy egy kép egy informatikai eszköz memóriájában vagy egy file-ban semmi más, mint egy bináris számsorozat. A képszerkesztő szoftverek abban segítenek nekünk, hogy értelmezik ezt a számsorozatot, vizuálisan megjelenítik a számunkra az általuk kódolt képet, és lehetővé teszik, hogy szintén vizuális eszközökkel ezen a képen módosítsunk. Egy Adobe Photoshop persze nagyon bonyolult képfeldolgozó algoritmusokat is megvalósít, de valójában a legalacsonyabb szinten nem történik más, mint bináris adatok manipulálása. Pontosan úgy, ahogy mi most azt manuálisan megtettük, képszerkesztő program nélkül.

Ebben a fejezetben tehát megismerkedtünk a számrendszerek fogalmával, és képet kaptunk arról, hogy az informatikai eszközök milyen módokon tárolják a különböző számokat. Ennek gyakorlati bemutatására megnéztük, hogy egy egyszerű BMP formátumú képet leíró file milyen byte-okat tartalmaz, illetve, hogy melyik byte-nak mi a jelentése a BMP szabvány szerint. Meg is változtattuk néhány képpont színét pusztán néhány byte átírásával. E rövid kitérő után a következő fejezetben az információátviteli rendszerek absztrakt modelljét építjük tovább, és ennek keretében megismerkedünk magával az átviteli csatornával, továbbá a vele kapcsolatban felmerülő problémákkal.