Hlavní navigace

Práce s rastrovými obrázky v knihovně OpenVG (okomentované demonstrační příklady)

25. 8. 2016
Doba čtení: 24 minut

Sdílet

Osmá část seriálu o knihovně OpenVG navazuje na obě části předchozí, v nichž jsme se seznámili s funkcemi pro operace s různými typy rastrových obrázků. Dnes budou tyto operace použity v šestici demonstračních příkladů.

1. Práce s rastrovými obrázky v knihovně OpenVG (okomentované demonstrační příklady)

2. Funkce vgCreateImage, vgDestroyImage a vgClearImage

3. Vykreslení obrázku funkcí vgDrawImage

4. Demonstrační příklad číslo 15: vytvoření, vybarvení a smazání rastrového obrázku

5. Použití funkce vgImageSubData pro přenos pixelů do obrázku

6. Demonstrační příklad číslo 16: nakreslení vzorku do rastrového obrázku

7. Přímé vykreslení rastrového obrázku funkcí vgWritePixels

8. Demonstrační příklad číslo 17: použití funkce vgWritePixels

9. Vytvoření screenshotu obrazovky funkcí vgReadPixels

10. Rastrový formát TGA

11. Rastrový formát BMP

12. Demonstrační příklad číslo 18: uložení screenshotu do rastrového formátu typu TGA (32bpp)

13. Demonstrační příklad číslo 19: použití formátu typu TGA s 24bpp

14. Demonstrační příklad číslo 20: použití formátu typu BMP (BitMap)

15. Repositář s demonstračními příklady

16. Odkazy na Internetu

1. Práce s rastrovými obrázky v knihovně OpenVG (okomentované demonstrační příklady)

Všechny funkce, které jsou nabízené knihovnou OpenVG pro provádění operací s rastrovými obrázky, jsme si podrobně popsali v předchozích dvou částech tohoto seriálu [1] [2], ovšem prozatím jsme si neukázali žádné praktické použití těchto funkcí. Proto si dnes na šesti demonstračních příkladech předvedeme, jakým způsobem je možné použít funkce vgCreateImage(), vgDestroyImage(), vgClearImage(), vgDrawImage(), vgImageSubData(), vgWritePixels() a taktéž vgReadPixels(). Poslední příklady obsahují funkce pro vytvoření screenshotu obrazovky, což je operace, která se může v mnoha případech hodit, zejména tehdy, pokud je vykreslování prováděno ve fullscreen režimu a není tedy možné využít další nástroje.

2. Funkce vgCreateImage, vgDestroyImage a vgClearImage

Základní funkcí, kterou použijeme pro vytvoření nového obrázku, je funkce nazvaná vgCreateImage(), která má následující hlavičku:

VGImage vgCreateImage(
    VGImageFormat format,
    VGint         width,
    VGint         height,
    VGbitfield    allowedQuality);

Této funkci se předá symbolická konstanta popisující formát uložení pixelů a způsob kódování barvy, dále pak rozměry obrázku (šířka a výška v pixelech) a posledním parametrem se ovlivňují ty operace, při nichž dochází k rotaci či ke změně měřítka bitmapy (tyto operace dnes nebudeme používat). Pokud je zaručeno mapování pixelů na obrazovku 1:1, nemá tento parametr žádný vliv.

Pro odstranění obrázku z paměti GPU se používá funkce nazvaná vgDestroyImage():

void vgDestroyImage(
    VGImage       image);

Ovšem ve chvíli, kdy je obrázek používán jiným objektem (cíl pro vykreslování, obrázek použitý pro vyplnění uzavřených cest atd.), dojde k jeho skutečnému uvolnění až ve chvíli, kdy jsou uvolněny i na obrázek navázané objekty (interně se tedy pro každý obrázek pamatuje příznak „může být automaticky odstraněn“). Ani tento problém nebudeme muset v demonstračních příkladech řešit, a tudíž tato funkce uvolní obrázek okamžitě.

Smazání či přesněji řečeno vybarvení zvolené obdélníkové části obrázku zajistí funkce nazvaná vgClearImage():

void vgClearImage(
    VGImage       image,
    VGint         x,
    VGint         y,
    VGint         width,
    VGint         height)

Tato funkce použije barvu nastavenou parametrem VG_CLEAR_COLOR a region, který se má vymazat, je specifikován osově orientovaným obdélníkem začínajícím na souřadnicích [x,y], jehož strany mají délku width a height. Vymazání je samozřejmě rychlejší, než přenos (konstantních) pixelů.

3. Vykreslení obrázku funkcí vgDrawImage

Ve chvíli, kdy je zapotřebí vykreslit obrázek na zvolenou kreslicí plochu (surface), zavolá se funkce nazvaná vgDrawImage(). Hlavička této funkce je skutečně velmi jednoduchá:

void vgDrawImage(
    VGImage       image);

Chování této funkce je do jisté míry podobné nám již známé funkci vgDrawPath() – ani zde se totiž nespecifikuje cíl vykreslování ani umístění obrázku na plochu. Obrázek může být vykreslen třemi způsoby: jako skutečný obrázek (jedná se tedy o operaci typu BitBLT), zapisované barvy pixelů se mohou násobit s nastavenou barvou (takto lze vykreslit poloprůhledný obrázek atd.) a dokonce je možné obrázek (pixely) použít jako masku (stencil) při vykreslování cest.

4. Demonstrační příklad číslo 15: vytvoření, vybarvení a smazání rastrového obrázku

V prvním demonstračním příkladu si ukážeme všechny čtyři výše zmíněné funkce nabízené knihovnou OpenVG. Úplný zdrojový kód tohoto příkladu lze nalézt na adrese https://github.com/tisnik/pre­sentations/tree/master/open­vg/example15, takže zde uvedu pouze ty nejzajímavější části.

Nejprve si ukažme použití funkcí vgCreateImage() a vgClearImage(). Ve funkci vgCreateImage() požadujeme formát uložení pixelů RGB (truecolor) bez alfa kanálu, takže se v prvním parametru této funkci předá symbolická konstanta VG_sRGBX_8888. Dále si povšimněte, že funkce vgClearImage() by se klidně mohla jmenovat vgFillRect() (narážka na SDL_FillRect), protože ji můžeme použít pro obarvení libovolné osově orientované obdélníkové části obrázku:

/*
 * Vytvoreni testovaci bitmapy.
 */
VGImage createImage(width, height)
{
    /* pouzije se RGB format bez alfa kanalu */
    VGImage image = vgCreateImage(VG_sRGBX_8888, width, height, VG_IMAGE_QUALITY_BETTER);
 
    /* prvni barva */
    VGfloat color1[4] = {1.0f, 0.0f, 0.0f, 1.0f};
    vgSetfv(VG_CLEAR_COLOR, 4, color1);
 
    /* obdelnik ci ctverec vykresleny prvni barvou */
    vgClearImage(image, 0, 0, width>>1, height>>1);
 
    /* druha barva */
    VGfloat color2[4] = {0.0f, 0.0f, 1.0f, 1.0f};
    vgSetfv(VG_CLEAR_COLOR, 4, color2);
 
    /* obdelnik ci ctverec vykresleny druhou barvou */
    vgClearImage(image, width>>1, height>>1, width>>1, height>>1);
 
    /* treti barva */
    VGfloat color3[4] = {1.0f, 1.0f, 1.0f, 0.5f};
    vgSetfv(VG_CLEAR_COLOR, 4, color3);
 
    /* obdelnik ci ctverec vykresleny treti barvou */
    vgClearImage(image, width>>2, height>>2, width>>1, height>>1);
 
    return image;
}

Při vykreslení obrázku se použije funkce vgDrawImage(). Případný přesun obrázku na jiné místo na obrazovce je provedeno přes transformační matici, což je téma dalšího pokračování tohoto seriálu:

/*
 * Vykresleni cele sceny.
 */
void draw(EGL_STATE_T *state, VGImage image)
{
    /* vymazani pozadi cernou barvou */
    VGfloat color1[4] = {0.0f, 0.0f, 0.0f, 1.0f};
    vgSetfv(VG_CLEAR_COLOR, 4, color1);
    vgClear(0, 0, state->window_width, state->window_height);
 
    /* vykresleni rastroveho obrazku */
    vgDrawImage(image);
 
    /* prohozeni predniho a zadniho bufferu (pokud je to zapotrebi) */
    eglSwapBuffers(state->display, state->surface);
}

Na samotném konci aplikace je nutné uvolnit obrázek z paměti GPU, což je v našem demonstračním příkladu implementováno ve funkci main():

    ...
    ...
    ...
    /* uvolneni rastroveho obrazku z pameti */
    vgDestroyImage(image);
 
    finalize_egl(&egl_state);

5. Použití funkce vgImageSubData pro přenos pixelů do obrázku

Velmi často potřebujeme provést přenos pixelů z operační paměti do vybraného rastrového obrázku. Pro tento účel se používá funkce pojmenovaná vgImageSubData() s následující hlavičkou:

void vgImageSubData(
     VGImage       image,
     const void   *data,
     VGint         dataStride,
     VGImageFormat dataFormat,
     VGint         x,
     VGint         y,
     VGint         width,
     VGint         height);

Ve skutečnosti se při přenosu pixelů mohou provádět i transformace pixelů mezi různými reprezentacemi (formát uložení a barvový model). Knihovna OpenVG totiž ví, jaký je formát již vytvořeného obrázku a ve funkci vgImageSubData se specifikuje formát zapisovaných pixelů, takže všechny údaje nutné pro konverzi jsou k dispozici. Dalším důležitým parametrem je parametr dataStride, kterým lze specifikovat mezery mezi jednotlivými obrazovými řádky (stride).

6. Demonstrační příklad číslo 16: nakreslení vzorku do rastrového obrázku

V dalším demonstračním příkladu, jehož úplný zdrojový kód i příslušný soubor Makefile určený pro řízení překladu naleznete na stránce https://github.com/tisnik/pre­sentations/tree/master/open­vg/example16, je ve funkci createImage() nejprve vytvořen obrázek o rozměrech width×height s formátem pixelů RGB bez alfa kanálu. Následně je v běžné operační paměti vytvořeno pole 256×256×4 bajty představující zdrojové hodnoty pixelů. Do tohoto pole se zapíšou barvy pixelů (dodržuje se formát RGB) a posléze se celé pole přenese do již připraveného obrázku funkcí vgImageSubData():

/*
 * Vytvoreni testovaci bitmapy.
 */
VGImage createImage(width, height)
{
    /* pouzije se RGB format bez alfa kanalu */
    VGImage image = vgCreateImage(VG_sRGBX_8888, width, height, VG_IMAGE_QUALITY_BETTER);
 
    /* oblast pameti pro ulozeni pixelu rastroveho obrazku */
    unsigned char *data = (unsigned char*)malloc(256*256*4);
    unsigned int x, y;
    unsigned char *p=data;
 
    /* vykresleni vzorku */
    for (y=0; y<256; y++) {
        for (x=0; x<256; x++) {
            *p++=0x00; /* alfa (ignorovano) */
            *p++=x;    /* blue */
            *p++=x+y;  /* green */
            *p++=y;    /* red */
        }
    }
 
    /* prenos barev pixelu do rastroveho obrazku */
    vgImageSubData(image, data, 256*4, VG_sRGBA_8888, 0, 0, 256, 256);
 
    /* oblast pameti muzeme uvolnit - jiz ji nepotrebujeme */
    free(data);
 
    return image;
}

Poznámka – povšimněte si nastavení parametru dataStride na hodnotu 256×4, což je délka obrazového řádku v bajtech. Pokud toto číslo změníte, bude výsledkem různě nakloněný vzorek.

7. Přímé vykreslení rastrového obrázku funkcí vgWritePixels

Pokud má programátor v operační paměti již přichystané pole s uloženými hodnotami (barvami) pixelů ve vhodném formátu, není nutné se snažit o vytvoření obrázku funkcí vgCreateImage() a následně do tohoto obrázku pixely přenést funkcí vgImageSubData(). Tuto možnost jsme si již ukázali, ale existuje i přímější cesta představovaná funkcí nazvanou vgWritePixels():

void vgWritePixels(
     const void    *data,
     VGint         dataStride,
     VGImageFormat dataFormat,
     VGint         dx,
     VGint         dy,
     VGint         width,
     VGint         height);

Této mnohdy velmi užitečné funkci se musí předat ukazatel na datovou oblast v operační paměti a formát uložení pixelů. Dalším důležitým parametrem je parametr dataStride, kterým lze specifikovat mezery mezi jednotlivými obrazovými řádky (stride). Kromě toho se specifikuje i velikost a umístění obdélníkové oblasti kreslicí plochy, do níž se bude obrázek přenášet. To znamená, že můžeme přímo určit, kam se má obdélníkové pole pixelů vykreslit.

8. Demonstrační příklad číslo 17: použití funkce vgWritePixels

Postup při použití výše zmíněné funkce vgWritePixels() je ukázán v dalším demonstračním příkladu. Všechny důležité prvky jsou součástí jediné funkce draw(), v níž se nejprve smaže zadní buffer funkcí vgClear(), vytvoří a naplní se pole v operační paměti (formát RGBA) a posléze se toto pole přenese do zadního bufferu funkcí vgWritePixels(). Povšimněte si, že bitmapa se ve skutečnosti vykreslí celkem třikrát – dvakrát v originální velikosti a jednou s poloviční výškou. Toho je dosaženo malým trikem – výška je snížena na 128 obrazových řádků z 256 původních řádků a vzdálenost mezi řádky je nastavena na hodnotu odpovídající offsetu mezi dvojicí řádků – vždy jeden obrazový řádek je tedy při vykreslování přeskočen:

/*
 * Vykresleni cele sceny.
 */
void draw(EGL_STATE_T *state)
{
    /* vymazani pozadi cernou barvou */
    VGfloat color1[4] = {0.0f, 0.0f, 0.0f, 1.0f};
    vgSetfv(VG_CLEAR_COLOR, 4, color1);
    vgClear(0, 0, state->window_width, state->window_height);
 
    /* oblast pameti pro ulozeni pixelu rastroveho obrazku */
    unsigned char *data = (unsigned char*)malloc(256*256*4);
    unsigned int   x, y;
    unsigned char *p=data;
 
    /* vykresleni vzorku */
    for (y=0; y<256; y++) {
        for (x=0; x<256; x++) {
            *p++=0x00; /* alfa (ignorovano) */
            *p++=x;    /* blue */
            *p++=x+y;  /* green */
            *p++=y;    /* red */
        }
    }
 
    /* prime vykresleni rastroveho obrazku */
    vgWritePixels(data, 256*4, VG_sRGBA_8888, 0, 0, 256, 256);
 
    /* vykresleni stejneho obrazku, ale o 300 pixelu napravo */
    vgWritePixels(data, 256*4, VG_sRGBA_8888, 300, 0, 256, 256);
 
    /* vykresleni obrazku polovicni vysky, ale o 300 pixelu vyse */
    /* - zmena "stride" */
    /* - zmena vysky */
    vgWritePixels(data, 256*4*2, VG_sRGBA_8888, 0, 300, 256, 128);
 
    /* oblast pameti muzeme uvolnit - jiz ji nepotrebujeme */
    free(data);
 
    /* prohozeni predniho a zadniho bufferu (pokud je to zapotrebi) */
    eglSwapBuffers(state->display, state->surface);
}

9. Vytvoření screenshotu obrazovky funkcí vgReadPixels

Při vytváření screenshotů či při provádění podobných operací se může hodit funkce nazvaná vgReadPixels(), která dokáže získat obsah části kreslicí plochy (či samozřejmě celou plochu, pokud je tak specifikováno) a uložit ji do již alokovaného regionu operační paměti. Tato funkce je opakem funkce vgWritePixels(), takže nás její parametry s velkou pravděpodobností nepřekvapí:

void vgReadPixels(
     void         *data,
     VGint         dataStride,
     VGImageFormat dataFormat,
     VGint         sx,
     VGint         sy,
     VGint         width,
     VGint         height);

Velmi důležité je, aby byla oblast pro zápis pixelů již dopředu alokováno v potřebné velikosti, jinak s velkou pravděpodobností dojde k pádu aplikace.

A právě tuto funkci použijeme v příkladech, v nichž se vytváří screenshoty obrazovky. Tyto příklady vlastně provedou pouze dvě operace:

  1. Přenos pixelů z obrazovky (bufferu) do operační paměti.
  2. Uložení pixelů do vhodného obrazového formátu.

Pro druhý krok je možné využít již existující knihovny, kterým se pouze předá vhodně dekódovaný obsah framebufferu, což je typický příklad aplikací v nichž se má framebuffer ukládat například do formátů GIF, JPEG či PNG (protože asi nemá smysl si psát vlastní komprimační rutiny, i když by to bylo z hlediska sebevzdělávání zajímavé). Alternativně lze použít jednodušší formáty (bez komprese), do nichž se budou obrazová data zapisovat v programové smyčce. Touto oblastí jsme se již zabývali při popisu framebufferu Raspberry Pi, kde jsme použili formáty PGM, PPM či PMA, dnes budou použity přece jen poněkud známější formáty TGA (Targa) a BMP (BitMaP).

10. Rastrový formát TGA

Grafický formát Targa (zkráceně TGA) byl již v poměrně dávné počítačové minulosti navržen firmou Truevision, která několik variant tohoto formátu využívala pro ukládání snímků získávaných pomocí svých video grabberů nazvaných Targa. Video grabber je, zjednodušeně řečeno, zařízení pro zachytávání a digitalizaci videa v reálném čase (tehdy s podporou specializovaného hardwaru), podobnou funkci nabízí dnešní televizní karty (TV-card). Grabberů typu Targa existovalo několik typů a pro každý typ byla vytvořena varianta vlastní grafického formátu TGA (lišily se především v počtu bitů na pixel).

Později, zejména se stále rostoucí oblibou formátu TGA mezi programátory i uživateli, došlo k dalšímu rozšíření variant způsobů ukládání pixelů a současně i k unifikaci, přičemž výsledkem je dnešní stav, kdy je možné formát TGA použít jak pro rastrové obrázky s barvovou paletou (palette-based images), tak i pro obrázky uložené ve stupních šedi (grayscale) či obrázky typu true color (224 barev). Podporován je i plnohodnotný osmibitový alfa kanál, pro některé aplikace si však vystačíme s jednobitovým alfa kanálem, resp. maskou průhlednosti.

Díky své jednoduchosti a širokým možnostem se grafický formát TGA značně rozšířil a byl použit v mnoha aplikacích, zejména těch, které musely pracovat s plnobarevnými obrázky. V minulosti, zejména v době operačního systému DOS na počítačích typu PC, se jednalo například o raytracery, ale i některé skenovací programy. Zajímavé je, že v různých aplikačních oblastech se prosadily i rozdílné grafické formáty – fotorealistická grafika byla většinou založena na TGA, desktop publishing se stále ještě v některých případech drží supersložitého formátu (resp. spíše pouhého kontejneru) TIFF, pro web se (logicky) používají formáty s dobrým komprimačním poměrem (PNG), ztrátovou kompresí (JPEG) či alespoň základní podporou pro animace (GIF).

Všechny informace jsou v souborech typu TGA rozděleny do čtyř sekcí, přičemž pouze první sekce je povinná, ostatní sekce mohou či nemusí být použity. Sekce jsou do značné míry podobné blokům v grafickém formátu GIF či chunkům ve formátu PNG, ovšem s tím rozdílem, že nemusí obsahovat identifikační hlavičku – pozice sekcí v souboru je možné zjistit již po načtení informační hlavičky souboru. Význam jednotlivých sekcí je následující:

  1. V první sekci umístěné na začátku souboru je uložena informační hlavička, jejíž velikost je vždy rovna 18 bytům. V hlavičce jsou uloženy základní informace o obraze, zejména jeho rozlišení, způsob kódování barev pixelů a orientace obrázku.
  2. Za informační hlavičkou může následovat identifikační pole obrázku, což je textový řetězec o maximální délce 255 znaků. Tato sekce je však nepovinná a málokdy se s ní v obrazových souborech setkáváme.
  3. Ve třetí sekci může být uložena barvová paleta. Tato sekce je, podobně jako sekce předchozí, opět nepovinná. Používá se pouze u některých obrázků s formátem 8 bitů na pixel (8bpp).
  4. V sekci čtvrté jsou uložena vlastní rastrová data, tj. barvy jednotlivých pixelů. Posloupnost rastrových dat (zejména orientaci vertikální osy) lze ve formátu TGA specifikovat přímo v hlavičce, je například možné obrázky ukládat od prvního řádku do řádku posledního či naopak (tak pracovaly původní video grabbery Targa). Rastrová data mohou být komprimována jednoduchým RLE algoritmem.

11. Rastrový formát BMP

Grafický formát BMP (BitMaP) patří v současnosti mezi nejpoužívanější grafické formáty, což je z technologického pohledu docela paradoxní, protože je poměrně složitý na zpracování a přitom nabízí pouze minimum užitečných vlastností, nicméně jeho nespornou výhodou je široká podpora v mnoha různých prohlížečích obrázků, konverzních utilitách, grafických editorech atd. Právě proto může být výhodné BMP pro screenshoty použít, pokud samozřejmě nechcete raději přilinkovat knihovnu pro práci s formáty PNG či JPEG (což vůbec není složité, i když mnohdy spíše zbytečné).

V čem však tkví taková rozšířenost BMP, pro kterou nemluví technologické parametry? Je to z prozaického důvodu: tento formát byl navržen firmami IBM a Microsoft (každá firma navrhla jinou variantu, jak jinak) jako základní rastrový obrazový formát pro jejich operační systémy (OS/2 a Microsoft Windows). Tím pádem je načítání i ukládání obrázků v tomto formátu podporováno přímo v aplikačním rozhraní daného operačního systému a tvůrci programů mohou toto rozhraní využít bez toho, aby daný formát detailně znali (poznámka: dnes WinAPI podporuje i další formáty, včetně JPEG). Obzvlášť zajímavé je ukládání interních obrázků aplikací v BMP, především dnes, kdy se rozdíl mezi rychlostí zpracování dat v CPU a rychlostí přenosu či ukládání dat na datová média stále zvyšuje – jinými slovy, výhodnější je provádět komprimaci náročnou na CPU než zatěžovat přenosové linky nebo datová média.

Rastrové (obrazové) soubory typu BMP jsou uloženy v takzvaném formátu nezávislém na zařízení (Device Independent Bitmap), ostatně místo zkratky BMP se v minulosti používala také zkratka DIB. Nejedná se o nic jiného, než o rastrový obrázek uložený způsobem, který není závislý na interních metodách práce s barvou nebo uspořádáním pixelů. Prakticky všechny dnes používané obrazové formáty jsou nezávislé na zařízení, mezi formáty závislé patří interní formát (či spíše pseudoformát) využívaný u digitálních fotoaparátů – jde o „surový“ (RAW) formát odpovídající datům přečteným ze světlocitlivé matice čipu CCD.

Formát BMP je navržen tak, že umožňuje ukládání rastrových dat ve čtyřech formátech:

  • 1 bit na pixel – dvoubarevné obrázky (používá se barevná paleta, nemusí se tedy jednat pouze o černobílé grafiky, ale o libovolnou kombinaci dvou barev)
  • 4 bity na pixel – 16barevné obrázky (taktéž se používá barevná paleta o délce 64 bytů, v minulosti nejpoužívanější typ, zejména na grafických kartách EGA a VGA)
  • 8 bitů na pixel – 256barevné obrázky (opět se používá barevná paleta, tentokrát o délce 1024 bytů)
  • 24 bitů na pixel – TrueColor obrázky (16 milionů barev, barevná paleta se nepoužívá, protože každý pixel je reprezentován přímo svou barvou).

Nás bude v kontextu OpenVG pochopitelně zajímat pouze poslední případ.

Každý korektní soubor typu BMP obsahuje následující datové struktury, jejichž název odpovídá C-čkovským strukturám definovaným ve WinAPI:

Název struktury Význam
BITMAPFILEHEADER hlavička BMP souboru
BITMAPINFOHEADER informační hlavička o obrázku
RGBQUAD[] tabulka barev (barvová paleta)
BITS pole bitů obsahujících vlastní rastrová data (pixely)

Význam těchto datových struktur je následující:

  1. BITMAPFILEHEADER: jedná se o datovou strukturu, která obsahuje základní informace o souboru typu BMP. Velikost této struktury je konstantní a má hodnotu 14 bytů.
  2. BITMAPINFOHEADER: tato datová struktura obsahuje základní metainformace o uloženém obraze. Velikost této struktury je opět konstantní, zde jde o 40 bytů.
  3. RGBQUAD[]: pole obsahující barvovou paletu ve formě složek RGB. Typická délka barvové palety, tj. počet barev, je 2, 16 a 256.
  4. BITS: v této datové struktuře jsou uložena vlastní obrazová data. Konkrétní formát těchto dat závisí na použité komprimační metodě (i na tom, zda je vůbec použita) a na celkovém počtu barev v obrázku.

Vícebajtové sekvence jsou uloženy ve formátu little-endian, tj. byte s nejvyšší váhou je uložený jako poslední. To platí jak pro dvoubajtové, tak i pro čtyřbajtové sekvence (a také pro barvovou paletu).

12. Demonstrační příklad číslo 18: uložení screenshotu do rastrového formátu typu TGA (32bpp)

Podívejme se nyní na to, jak lze pro vytvoření screenshotu použít formát TGA s 32bitovou hloubkou. Nejprve si připravíme pole s hlavičkou obrázku a předvyplněnými hodnotami:

/*
 * Hlavicka souboru TGA (Targa)
 */
unsigned char true_color_tga_header[] =
{
    0x00,       /* typ hlavicky formatu typu TGA */
    0x00,       /* nepouzivame paletu */
    0x02,       /* typ obrazku je RGB TrueColor */
    0x00, 0x00, /* delka barvove palety je nulova */
    0x00, 0x00, /* pozice v barvove palete */
    0x00,       /* pocet bitu na jeden prvek v palete */
    0x00, 0x00,
    0x00, 0x00, /* obrazek je umisteny na pozici [0,0] */
    0x00, 0x00, /* sirka obrazku */
    0x00, 0x00, /* vyska obrazku */
    0x20,       /* format je 32 bitu na pixel */
    0x20        /* orientace bitmapy v obrazku */
};

Vlastní uložení pak zprostředkovává funkce savePixmap(). V ní se vyplní dva údaje do hlavičky – šířka a výška bitmapy – a následně se bez jakékoli komprimace zapíšou jednotlivé pixely:

/*
 * Ulozeni pixmapy do externiho souboru
 */
void savePixmap(const unsigned int width, const unsigned int height,
                const unsigned char *pixels, const char *fileName)
{
    FILE *fout;
    unsigned int j;
 
    /* do hlavicky TGA zapsat velikost obrazku */
    true_color_tga_header[12]=(width) & 0xff;
    true_color_tga_header[13]=(width) >>8;
    true_color_tga_header[14]=(height) & 0xff;
    true_color_tga_header[15]=(height) >>8;
 
    fout=fopen(fileName, "wb");
    if (fout) {
        fwrite(true_color_tga_header, 18, 1, fout); /* zapsat hlavicku TGA */
        for (j=height; j; j--) {                /* radky zapisovat v opacnem poradi */
            unsigned int yoff=(j-1)*4*width;    /* y-ovy offset v poli */
            fwrite(pixels+yoff, width*4, 1, fout);
        }
        fclose(fout);
    }
}

Poznámka – jak je z kódu patrné, zapisují se vždy celé obrazové řádky, což je relativně rychlá operace.

Ještě se podívejme na to, jakým způsobem lze přečíst jednotlivé pixely a přenést je z framebufferu do operační paměti:

    unsigned int width  = state->window_width;
    unsigned int height = state->window_height;
    unsigned char *data = (unsigned char*)malloc(width*height*4);
 
    /* precist obsah obrazovky */
    vgReadPixels(data, width * 4, VG_sXRGB_8888, 0, 0, width, height);

13. Demonstrační příklad číslo 19: použití formátu typu TGA s 24bpp

Poměrně běžné je použití obrázků s 24bitovou hloubkou, což představuje menší problém, protože tento formát grafická knihovna OpenVG nepodporuje. Screenshot se tedy provede s původní 32bitovou hloubkou:

    unsigned int width  = state->window_width;
    unsigned int height = state->window_height;
    unsigned char *data = (unsigned char*)malloc(width*height*4);
 
    /* precteni obsahu cele obrazovky do pametove oblasti 'data' */
    /* format je 32bitovy */
    vgReadPixels(data, width * 4, VG_sBGRX_8888, 0, 0, width, height);

Před zápisem či přímo při zápisu je však nutné pixely vhodným způsobem zkonvertovat. Pomalý způsob může vypadat takto:

/*
 * Ulozeni pixmapy do externiho souboru
 */
void savePixmap(const unsigned int width, const unsigned int height,
                const unsigned char *pixels, const char *fileName)
{
    FILE *fout;
    unsigned int i, j, k;
 
    /* do hlavicky TGA zapsat velikost obrazku */
 
    /* sirka bitmapy je ulozena ve dvou bajtech */
    true_color_tga_header[12]=(width) & 0xff;
    true_color_tga_header[13]=(width) >>8;
 
    /* vyska bitmapy je ulozena taktez ve dvou bajtech */
    true_color_tga_header[14]=(height) & 0xff;
    true_color_tga_header[15]=(height) >>8;
 
    fout=fopen(fileName, "wb");
    if (fout) {
        fwrite(true_color_tga_header, 18, 1, fout);     /* zapsat hlavicku TGA do souboru */
        for (j=height; j; j--) {                        /* radky zapisovat v opacnem poradi */
            unsigned int yoff=(j-1)*4*width;            /* y-ovy offset v poli */
            unsigned int xoff=0;                        /* x-ovy offset v poli */
 
            /* Poznamka: v nasledujicim demonstracnim prikladu bude tato */
            /*           programova smycka vyresena mnohem efektivnejsim zpusobem */
            for (i=0; i<width; i++) {                   /* pro kazdy pixel na radku */
                for (k=0; k<3; k++) {                   /* prohodit barvy RGB na BGR */
                    fputc(pixels[xoff+yoff+3-k], fout); /* a zapsat do souboru */
                }                                       /* (zde velmi primitivne a neefektivne bajt po bajtu) */
                xoff+=4;                                /* posun na dalsi pixel */
                                                        /* (zdrojova data maji 32 bitu na pixel */
            }
        }
        fclose(fout);
    }
}

Poznámka: tento způsob je skutečně relativně pomalý. Na Raspberry Pi (původní model B s jednojádrovým mikroprocesorem) trvá uložení screenshotu obrazovky o rozlišení 1280×1024 pixelů cca 1,5 sekundy.

14. Demonstrační příklad číslo 20: použití formátu typu BMP (BitMap)

Poněkud efektivnější způsob konverze 32bpp→24bpp je ukázán v dnešním posledním demonstračním příkladu, v němž se screenshot uloží do souboru typu BMP (BitMaP). Opět budeme potřebovat předvyplněnou hlavičku, do níž se posléze doplní jen některé variabilní údaje – šířka a výška obrázku:

/*
 * Hlavicka souboru BMP (BitMap)
 */
unsigned char bmp_header[] =
{
    0x42, 0x4d,             /* magicke cislo */
    0x46, 0x00, 0x00, 0x00, /* velikost hlavicky=70 bajtu */
    0x00, 0x00,             /* nepouzito */
    0x00, 0x00,             /* nepouzito */
    0x36, 0x00, 0x00, 0x00, /* 54 bajtu - offset na zacatek vlastnich dat */
    0x28, 0x00, 0x00, 0x00, /* 40 bajtu - velikost hlavicky DIB */
    0x00, 0x00, 0x00, 0x00, /* sirka bitmapy */
    0x00, 0x00, 0x00, 0x00, /* vyska bitmapy */
    0x01, 0x0,              /* 1 pixel plane */
    0x18, 0x00,             /* 24 bpp */
    0x00, 0x00, 0x00, 0x00, /* bez komprese */
    0x00, 0x00, 0x00, 0x00, /* velikost pole pixelu */
    0x13, 0x0b, 0x00, 0x00, /* 2835 pixels/meter (vetsina SW ignoruje) */
    0x13, 0x0b, 0x00, 0x00, /* 2835 pixels/meter (vetsina SW ignoruje)  */
    0x00, 0x00, 0x00, 0x00, /* barvova paleta */
    0x00, 0x00, 0x00, 0x00, /* pocet dulezitych barev v palete (v 24 bpp ignorujeme) */
};

Samotné uložení bitmapy může být naprogramováno tak, že se vždy zapíše celá trojice bajtů tvořících barvu jednoho pixelu a posléze se ukazatel přesune o hodnotu 4 (nikoli 3), tedy na začátek dalšího pixelu:

CS24_early

/*
 * Ulozeni pixmapy do externiho souboru
 */
void savePixmap(const unsigned int width, const unsigned int height,
                const unsigned char *pixels, const char *fileName)
{
    FILE *fout;
    unsigned int i;
 
    /* sirka bitmapy je zapsana ve ctverici bajtu */
    bmp_header[18] =  width & 0xff;
    bmp_header[19] = (width >> 8) & 0xff;
    bmp_header[20] = (width >> 16) & 0xff;
    bmp_header[21] = (width >> 24) & 0xff;
 
    /* vyska bitmapy je taktez zapsana ve ctverici bajtu */
    bmp_header[22] =  height & 0xff;
    bmp_header[23] = (height >> 8) & 0xff;
    bmp_header[24] = (height >> 16) & 0xff;
    bmp_header[25] = (height >> 24) & 0xff;
 
    fout=fopen(fileName, "wb");
    if (fout) {
                                           /* zapis hlavicky */
        fwrite(bmp_header, sizeof(bmp_header), 1, fout);
        unsigned char *p=pixels+1;         /* adresa prvniho pixelu (preskocime alfa slozku) */
 
        /* nyni zapiseme celou bitmapu do souboru, */
        /* z formatu 32bpp se jednoduchou konverzi vytvori format 24bpp */
        for (i=0; i<width*height; i++) {/* projit celou zdrojovou bitmapou */
            fwrite(p, 3, 1, fout);         /* zapisujeme po cele trojici RGB */
            p+=4;                          /* posun na dalsi pixel ve zdrojovych datech */
        }
        fclose(fout);
    }
}

15. Repositář s demonstračními příklady

Všech šest demonstračních příkladů, které jsme si v dnešním článku popsali, bylo uloženo do Git repositáře dostupného na adrese https://github.com/tisnik/pre­sentations. V tabulce zobrazené pod tímto odstavcem naleznete na zdrojové kódy všech šesti zmíněných demonstračních příkladů přímé odkazy:

Poznámka: pro zjednodušení překladu je ke každému demonstračnímu příkladu přiložen i příslušný soubor Makefile (otestovaný na Raspberry Pi).

16. Odkazy na Internetu

  1. Alpha matting & premultiplication
    http://dvd-hq.info/alpha_matting.php
  2. Alpha compositing (Wikipedia)
    https://en.wikipedia.org/wi­ki/Alpha_compositing#Alpha_blen­ding
  3. So What's the Big Deal with Horizontal and Vertical Bezier Handles Anyway? (pro grafiky)
    http://theagsc.com/blog/tutorials/so-whats-the-big-deal-with-horizontal-vertical-bezier-handles-anyway/
  4. EGL quick reference card
    https://www.khronos.org/files/egl-1–4-quick-reference-card.pdf
  5. EGL Reference Pages Index
    https://www.khronos.org/re­gistry/egl/sdk/docs/man/html/in­dexflat.php
  6. Funkce eglInitialize
    https://www.khronos.org/re­gistry/egl/sdk/docs/man/html/e­glInitialize.xhtml
  7. Funkce eglGetDisplay
    https://www.khronos.org/re­gistry/egl/sdk/docs/man/html/e­glGetDisplay.xhtml
  8. Funkce eglGetConfigs
    https://www.khronos.org/re­gistry/egl/sdk/docs/man/html/e­glGetConfigs.xhtml
  9. Funkce eglGetConfigAttrib
    https://www.khronos.org/re­gistry/egl/sdk/docs/man/html/e­glGetConfigAttrib.xhtml
  10. Funkce eglDestroySurface
    https://www.khronos.org/re­gistry/egl/sdk/docs/man/html/e­glDestroySurface.xhtml
  11. Funkce eglDestroyContext
    https://www.khronos.org/re­gistry/egl/sdk/docs/man/html/e­glDestroyContext.xhtml
  12. Funkce eglTerminate
    https://www.khronos.org/re­gistry/egl/sdk/docs/man/html/e­glTerminate.xhtml
  13. Khronos Native Platform Graphics Interface
    https://www.khronos.org/re­gistry/egl/specs/eglspec.1­.4.pdf
  14. Khronos Group
    https://www.khronos.org/
  15. Khronos Group (Wikipedia)
    https://en.wikipedia.org/wi­ki/Khronos_Group
  16. Raspberry Pi VideoCore APIs
    http://elinux.org/Raspberry_Pi_Vi­deoCore_APIs
  17. Programming AudioVideo on the Raspberry Pi GPU
    https://jan.newmarch.name/RPi/in­dex.html
  18. The Standard for Vector Graphics Acceleration
    https://www.khronos.org/openvg/
  19. OpenVG (Wikipedia)
    https://en.wikipedia.org/wiki/OpenVG
  20. OpenVG Quick Reference Card
    https://www.khronos.org/files/openvg-quick-reference-card.pdf
  21. OpenVG on the Raspberry Pi
    http://mindchunk.blogspot­.cz/2012/09/openvg-on-raspberry-pi.html
  22. ShivaVG: open-source ANSI C OpenVG
    http://ivanleben.blogspot­.cz/2007/07/shivavg-open-source-ansi-c-openvg.html
  23. Testbed for exploring OpenVG on the Raspberry Pi
    https://github.com/ajstarks/openvg
  24. Programovací jazyky a knihovny určené pro výuku základů počítačové grafiky: knihovna Pygame
    http://mojefedora.cz/programovaci-jazyky-a-knihovny-urcene-pro-vyuku-zakladu-pocitacove-grafiky-knihovna-pygame/
  25. Programovací jazyky a knihovny určené pro výuku základů počítačové grafiky: knihovna Pygame prakticky
    http://mojefedora.cz/programovaci-jazyky-a-knihovny-urcene-pro-vyuku-zakladu-pocitacove-grafiky-knihovna-pygame-prakticky/
  26. Programovací jazyky a knihovny určené pro výuku základů počítačové grafiky: práce s bitmapami a TrueType fonty
    http://mojefedora.cz/programovaci-jazyky-a-knihovny-urcene-pro-vyuku-zakladu-pocitacove-grafiky-prace-s-bitmapami-a-truetype-fonty/
  27. Programovací jazyky a knihovny určené pro výuku základů počítačové grafiky: sprity v knihovně Pygame
    http://mojefedora.cz/programovaci-jazyky-a-knihovny-urcene-pro-vyuku-zakladu-pocitacove-grafiky-sprity-v-knihovne-pygame/
  28. Programovací jazyky a knihovny určené pro výuku základů počítačové grafiky: detekce kolize spritů
    http://mojefedora.cz/programovaci-jazyky-a-knihovny-urcene-pro-vyuku-zakladu-pocitacove-grafiky-detekce-kolize-spritu/
  29. Programovací jazyky a knihovny určené pro výuku základů počítačové grafiky: transformace rastrových obrázků
    http://mojefedora.cz/programovaci-jazyky-a-knihovny-urcene-pro-vyuku-zakladu-pocitacove-grafiky-transformace-rastrovych-obrazku/
  30. Seriál Grafické karty a grafické akcelerátory
    http://www.root.cz/serialy/graficke-karty-a-graficke-akceleratory/
  31. Grafika na osmibitových počítačích firmy Sinclair II
    http://www.root.cz/clanky/grafika-na-osmibitovych-pocitacich-firmy-sinclair-ii/
  32. Xiaolin_Wu's Line Algorithm
    https://en.wikipedia.org/wi­ki/Xiaolin_Wu's_line_algo­rithm
  33. Grafické čipy v osmibitových počítačích Atari
    http://www.root.cz/clanky/graficke-cipy-v-osmibitovych-pocitacich-atari/
  34. Osmibitové počítače Commodore a čip VIC-II
    http://www.root.cz/clanky/osmibitove-pocitace-commodore-a-cip-vic-ii/
  35. Grafika na osmibitových počítačích firmy Apple
    http://www.root.cz/clanky/grafika-na-osmibitovych-pocitacich-firmy-apple/
  36. Počátky grafiky na PC: grafické karty CGA a Hercules
    http://www.root.cz/clanky/pocatky-grafiky-na-pc-graficke-karty-cga-a-hercules/
  37. Karta EGA: první použitelná barevná grafika na PC
    http://www.root.cz/clanky/karta-ega-prvni-pouzitelna-barevna-grafika-na-pc/
  38. Grafické karty MCGA a VGA
    http://www.root.cz/clanky/graficke-karty-mcga-a-vga/
  39. Grafický subsystém počítačů Amiga
    http://www.root.cz/clanky/graficky-subsystem-pocitacu-amiga/
  40. Grafický subsystém počítačů Amiga II
    http://www.root.cz/clanky/graficky-subsystem-pocitacu-amiga-ii/
  41. Raspberry Pi pages
    https://www.raspberrypi.org/
  42. BCM2835 registers
    http://elinux.org/BCM2835_registers
  43. VideoCore (archiv stránek společnosti Alphamosaic)
    http://web.archive.org/web/20030209213838/www­.alphamosaic.com/videocore/
  44. VideoCore (Wikipedia)
    https://en.wikipedia.org/wi­ki/Videocore
  45. RPi lessons: Lesson 6 Screen01
    http://www.cl.cam.ac.uk/pro­jects/raspberrypi/tutorial­s/os/screen01.html
  46. Raspberry Pi forum: Bare metal
    https://www.raspberrypi.or­g/forums/viewforum.php?f=72
  47. C library for Broadcom BCM 2835 as used in Raspberry Pi
    http://www.airspayce.com/mi­kem/bcm2835/
  48. Raspberry Pi Hardware Components
    http://elinux.org/RPi_Har­dware#Components
  49. (Linux) Framebuffer
    http://wiki.linuxquestion­s.org/wiki/Framebuffer
  50. (Linux) Framebuffer HOWTO
    http://tldp.org/HOWTO/Framebuffer-HOWTO/
  51. Linux framebuffer (Wikipedia)
    https://en.wikipedia.org/wi­ki/Linux_framebuffer
  52. RPi Framebuffer
    http://elinux.org/RPi_Framebuffer
  53. HOWTO: Boot your Raspberry Pi into a fullscreen browser kiosk
    http://blogs.wcode.org/2013/09/howto-boot-your-raspberry-pi-into-a-fullscreen-browser-kiosk/
  54. Zdrojový kód fb.c pro RPI
    https://github.com/jncronin/rpi-boot/blob/master/fb.c
  55. RPiconfig
    http://elinux.org/RPi_config.txt
  56. Mailbox framebuffer interface
    https://github.com/raspbe­rrypi/firmware/wiki/Mailbox-framebuffer-interface
  57. Seriál Grafické formáty
    http://www.root.cz/serialy/graficke-formaty/
  58. Vykreslovací pipeline OpenVG (schéma)
    https://www.khronos.org/as­sets/uploads/apis/openvg_pi­peline1.jpg
  59. sRGB
    https://cs.wikipedia.org/wiki/SRGB

Byl pro vás článek přínosný?

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.