Hlavní navigace

Operace s framebufferem na Raspberry Pi (dokončení)

11. 2. 2016
Doba čtení: 45 minut

Sdílet

Ve čtvrtém článku o použití framebufferu na Raspberry Pi se seznámíme s vytvářením screenshotů framebufferu, s algoritmem pro vykreslování úseček s vyhlazováním hran a taktéž s jedním jednoduchým NPR algoritmem.

Obsah

1. Operace s framebufferem na Raspberry Pi (dokončení)

2. Vytvoření screenshotu framebufferu

3. Volba vhodného formátu – rodina P[BGPA]M

4. Textová varianta Portable PixelMap File Format (PPM)

5. Binární varianta Portable PixelMap File Format (PPM)

6. Konverze barev

7. Úplný zdrojový kód prvního demonstračního příkladu

8. Vykreslování úseček s aplikací antialiasingu

9. Úplný zdrojový kód druhého demonstračního příkladu

10. Třešnička na závěr: non-photorealistic rendering

11. Úplný zdrojový kód třetího demonstračního příkladu

12. Pokračování? GLES

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

14. Odkazy na Internetu

1. Operace s framebufferem na Raspberry Pi (dokončení)

Ve čtvrté a současně i poslední části miniseriálu o použití framebufferu na oblíbených jednodeskovým mikropočítačích Raspberry Pi, navážeme na všechny tři předchozí části [1][2][3], protože si na několika demonstračních příkladech vysvětlíme, jak je možné vytvořit screenshot framebufferu, jak lze vykreslovat úsečky s aplikací antialiasingu a vyhlazování hran a na závěr si ukážeme použití jednoho (velmi jednoduchého) algoritmu z oblasti NPR (Non-Photorealistic Rendering), který bude využívat vykreslování úseček s vyhlazováním hran. V navazujících částech se již budeme zabývat jiným tématem, a to konkrétně použitím knihovny GLES (OpenGL ES), samozřejmě taktéž na Raspberry Pi.

2. Vytvoření screenshotu framebufferu

První problém, s jehož řešením se dnes seznámíme, je vlastně na první pohled zcela jednoduchý – ve chvíli, kdy je provedeno vykreslení nějakého obrazce do framebufferu je v některých případech (například pro potřeby tohoto článku) nutné udělat screenshot, tj. uložit barvy jednotlivých pixelů z framebufferu do externího souboru. Celý problém se tedy rozděluje na tři části:

  1. Přečtení všech pixelů z framebufferu. To již bezpečně umíme, a to jak na úrovni příkazové řádky, tak i z céčkového programu při namapování speciálního zařízení /dev/fb0 do paměti.
  2. Dekódování barev pixelů. Tuto problematiku už dobře známe z druhé strany, protože již umíme převést hodnoty RGB například i do framebufferu s 16bitovou hloubkou (což je pro Raspberry Pi výchozí hodnota). Musíme se tedy pouze naučit zpětný převod.
  3. Uložení barev do vhodného souborového formátu. Pokud je zvolen formát s „rozumnou“ hlavičkou a bez použití komprimace, je řešení velmi jednoduché, jak ostatně uvidíme v následujících kapitolách.

3. Volba vhodného formátu – rodina P[BGPA]M

Pro export obsahu framebufferu do zvoleného souboru můžeme použít buď vlastní implementaci funkce či několika spolupracujících funkcí, které zajistí vytvoření souboru ve vhodném formátu, nebo je možné pro tento účel využít již existující knihovny, kterým se pouze předá vhodně dekódovaný obsah framebufferu. Druhou možnost je výhodnější použít ve chvíli, pokud 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é). Vzhledem k tomu, že se v tomto seriálu zaměřujeme na ta nejsnáze použitelná řešení, nebudeme se zabývat již hotovými knihovnami (v případě zájmu hledejte libpng, libgif, libjpeg), ale vytvoříme si namísto toho vlastní funkci pro export rastrových dat. Teoreticky by bylo možné využít formáty TGA (Targa) či BMP (Bitmap), ovšem ve skutečnosti existují ještě lepší formáty, které jsou velmi snadno implementovatelné a především konvertovatelné do formátů jiných. Jedná se o rodinu formátů P[BGPA]M.

Konkrétně se pod tímto názvem skrývají čtyři příbuzné rastrové grafické formáty PBM, PGM, PPM a PAM. Ty vznikly na původních unixových systémech a ne pro potřeby ukládání obrázků na počítačích řady PC (dnes jsou tyto formáty samozřejmě používány i na dalších platformách). Unixový původ je na těchto formátech znát, zejména jejich téměř absolutní nezávislost na použitých hardwarových prostředcích (nenastávají například prakticky žádné problémy s little vs. big endian, zejména u textových verzí formátů), přítomnost magického čísla (jednoznačné identifikace formátu) na začátku souboru, použití textových hlaviček, dostupnost dokumentace ve formě manuálových stránek apod. Není také použita žádná metoda komprimace rastrových dat, protože na tuto činnost jsou v unixových systémech (ale i jinde) určeny dnes již téměř standardní prostředky typu gzip, bzip, bzip2 apod., běžné bývalo i použití starodávného filtru compress (ten byl mimochodem zatížen stejným patentem jako GIF), takže samotné grafické formáty nebylo nutné zbytečně komplikovat – obrázek bylo možné přes rouru zkomprimovat či naopak dekomprimovat přesně podle potřeb uživatele.

Popisované grafické formáty existují ve dvou variantách – textové (označované běžně jako ASCII či Plain) a binární (označovaná jako Raw). Rozdíl mezi těmito dvěma variantami (včetně předností a záporů) je zřejmý:

  1. V případě textové varianty jsou vytvářeny přibližně čtyřikrát větší soubory, takto uložené obrázky jsou však velmi snadno zpracovatelné i programovými prostředky, které jsou primárně určeny pro zpracování textových souborů (mezi tyto prostředky patří například sh, awk, sed, Perl a Tcl). I vytváření textové varianty je v některých případech jednodušší, mnohdy postačuje přesměrování standardního výstupu aplikace do souboru s následným přidáním hlavičky. Vytvořené textové soubory jsou bez problémů přenositelné téměř jakýmkoli protokolem (včetně sedmibitových e-mailů bez nutnosti použití base-64 apod.). Délka řádku je stanovena na maximálně 70 znaků, což dovoluje zpracování jakýmkoli textovým editorem.
  2. Binární varianta těchto grafických formátů (raw) přináší dvě přednosti: menší velikost souboru v porovnání s variantou textovou a snadné načtení rastrového obrázku (uloženého za hlavičkou) pomocí jednoho příkazu, například funkce fread() ze standardní céčkové knihovny. Samotný zápis obrázku je dokonce jednodušší než zápis souboru typu TGA (jde také o soubor s velmi jednoduchou strukturou), a to zejména z toho důvodu, že není potřeba brát ohled na endianitu dané architektury ani zarovnání datových struktur; oboje nám odstíní céčkovská knihovna. Pro čtení či zápis hlavičky je možné použít funkce typu scanf() a printf().

Na úvod mi zbývá vysvětlit, proč se jedná o čtyři formáty a ne o formát jediný. Důvod je pochopitelný – již z koncovky souboru s rastrovým obrázkem (v případě PBM, PGM a PPM) je možné poznat, jakou bitovou hloubku a tím i maximální počet barev má daný obrázek. Také samotná hlavička obrázku i jeho interní struktura může být mnohem jednodušší. Poslední ze čtveřice, tj. formát PAM, se v tomto ohledu poněkud odlišuje, protože se jedná o ideového následníka PBM, PGM a PPM, který do jedné struktury souboru integruje mnoho různých formátů pixelů. V následující tabulce jsou sepsány názvy všech dnes popisovaných formátů, včetně bitové hloubky obrázků (bpp – bits per pixel). Z bitové hloubky je možné odvodit i maximální počet barev zobrazitelných v jednom obrázku.

Koncovka souboru Typ Bpp Počet barev
PBM Portable Bitmap File Format 1 2 (černá a bílá)
PGM Portable GrayMap File Format 8 256 odstínů šedi (základ)
    16 max. 65536 odstínů šedi max.
PPM Portable PixelMap File Format 24 16777216
    48 max. 281474976710656 max.
PAM Portable Arbitrary Map 1–24 2–16777216

Všechny čtyři formáty jsou v dokumentaci (například k ImageMagicku či Netpbm) souhrnně označované jako PNM. V tomto případě se však nejedná o žádný konkrétní formát, pouze o zkrácený výpis všech zkratek. Jinými slovy, na grafický rastrový soubor s koncovkou PNM bychom měli narazit pouze v případě, že se interně jedná o PBM, PGM či PPM (to se jednoduše pozná z hlavičky, ve které je uloženo magické číslo). Vzhledem ke zmatkům ohledně PNM jsem si proto dovolil v názvu této kapitoly použít regulární výraz.

4. Textová varianta Portable PixelMap File Format (PPM)

Soubory typu PPM neboli Portable PixelMap umožňují práci s obrázky v truecolor formátu používajících klasický barvový model RGB (Red, Green, Blue). Zatímco většina „truecolor“ formátů pracuje s maximálním množstvím 256×256×256=16777216 barev, u PPM je teoreticky možné použít až neuvěřitelných 65536×65536×65536=281474976710656 barevných odstínů (ovšem délka těchto souborů je také neuvěřitelná :-). Hlavička textové varianty formátu PPM má tvar:

P3 (případné bílé znaky)
šířka (případné bílé znaky)
výška (případné bílé znaky)
maximální hodnota barvové složky pixelu (případné bílé znaky)
rastrová data v textovém tvaru

Barvové složky pixelů jsou uloženy s gamma faktorem nastaveným na 2,16, ovšem některé materiály zmiňují gamma faktor=2,2. Nekorektně napsané aplikace předpokládají, že mezi intenzitou barvové složky a její hodnotou je lineární vztah, takto vzniklé obrázky však postrádají kontrast v oblasti vyšších intenzit a také se snižuje dynamika (to vadí zejména u obrázků, které jsou již původně nekvalitní, například příliš tmavé či světlé). Následuje ukázka začátku souboru s truecolor obrázkem Lenny:

P3
# Created by IrfanView
256 256
255
224 136 122 224 136 122 224 136 124
...
...
...

5. Binární varianta Portable PixelMap File Format (PPM)

Binární forma souborů typu PPM nepřináší nic převratného. V hlavičce došlo ke změně magického čísla na „P6“ (textová varianta má „P3“) a pixely jsou podle nastavené maximální hodnoty (0 až 216) uloženy buď pomocí trojice bajtů RGB, nebo jako šestice RRGGBB. Celková délka souboru v případě trojice bytů na pixel (24 bpp) přibližně odpovídá nekomprimovaným souborům TGA či BMP (většinou budou soubory BMP větší, protože u nich jsou řádky zarovnávány na hodnotu dělitelnou čtyřmi). V případě plnobarevného obrázku Lenny má soubor délku 196646 bytů, což je oproti čistému rastrovému obrázku (256×256×3=196608) pouze nepatrné zvýšení.

Ani při práci s binárním formátem nesmíme zapomenout na gamma faktor, který má hodnotu 2,16. Pro maximální hodnotu barvových složek do 256 (maximálně 16 milionů barev) je většinou vhodnější provádět gamma korekci s využitím vyhledávací tabulky (look-up table), pro větší maximální hodnoty však velikost tabulky narůstá a proto se používají sice pomalejší, ale paměťově zcela nenáročné konverzní funkce. Při požadavku na rychlé (a poněkud nepřesné) náhledy se také používá aproximace gamma funkce (což je exponenciála a při inverzní transformaci logaritmická funkce) dvojicí lineárních úseků.

Pro naše účely – uložení obsahu framebufferu – využijeme právě tento souborový formát, takže export bude vypadat přibližně takto:

void saveFramebuffer(const char *filename, FramebufferInfo *framebufferInfoPtr, char *pixels)
{
    const int bpp = framebufferInfoPtr->bits_per_pixel;
    const int xres = framebufferInfoPtr->xres;
    const int yres = framebufferInfoPtr->yres;
 
    FILE *fout = fopen(filename, "wb");
    int i;
    char *adr=pixels;
 
    if (!fout) {
        perror("Unable to open output file");
        return;
    }
 
    /* hlavicka souboru s rastrovym obrazkem */
    fprintf(fout, "P6\n");
    fprintf(fout, "%d\n%d\n255\n", xres, yres);
 
    /* zde bude kód, který v binární podobě zapíše hodnoty jednotlivých pixelů */
    ...
    ...
    ...
    /* zde bude kód, který v binární podobě zapíše hodnoty jednotlivých pixelů */
 
    fclose(fout);
}

6. Konverze barev

Číst jednotlivé pixely z framebufferu již umíme, souborový formát pro uložení těchto dat je taktéž zvolený, takže nám zbývá „pouze“ zajistit konverzi barev. Pokud je framebuffer Raspberry Pi nakonfigurován na použití framebufferu o hloubce 24bpp či 32bpp, je konverze barev a následný export dat triviální. Pouze si musíme dát pozor na to, že u 32bitové hloubky se poslední bajt každého pixelu do výsledného souboru neukládá (viz příkaz adr+=3; a adr+=4;):

const int bpp = framebufferInfoPtr->bits_per_pixel;
const int xres = framebufferInfoPtr->xres;
const int yres = framebufferInfoPtr->yres;
 
int i;
char *adr=pixels;
 
switch (bpp) {
    case 24: /* 24bitova barvova hloubka - lze nahradit jedinym zapisem, pokud nebudete prehazovat barvy! */
        for (i=0; i<xres*yres; i++) {
            fwrite(adr+2, 1, 1, fout);
            fwrite(adr+1, 1, 1, fout);
            fwrite(adr+0, 1, 1, fout);
            adr+=3; /* posun na dalsi pixel ve framebufferu */
        }
        break;
    case 32: /* 32bitova barvova hloubka */
        for (i=0; i<xres*yres; i++) {
            fwrite(adr+2, 1, 1, fout);
            fwrite(adr+1, 1, 1, fout);
            fwrite(adr+0, 1, 1, fout);
            adr+=4; /* posledni bajt se busi preskocit, neuklada se */
        }
        break;
}

Komplikovanější (čti „zajímavější“) je situace v případě, že je použit framebuffer se šestnáctibitovou hloubkou, což je pro Raspberry Pi výchozí nastavení. Zde je nutné provést tři kroky – načíst dvojici sousedních bajtů, v nichž je uložena barva pixelu, spojit tyto dva bajty do jediného slova a nakonec provést opětovné rozdělení na tři barvové složky R,G,B. Připomeňme si, že u Raspberry Pi je použit formát 565, tj. modrá a červená složka pixelu je uložena v pěti bitech a zelená složka v šesti bitech. Při bitové aritmetice budou využity tyto konstanty:

/*
 * Plati pro format 565
 */
#define RED_OFFSET     11    /* posun v rámci 16bitové hodnoty pixelu */
#define GREEN_OFFSET    5
#define BLUE_OFFSET     0
#define RED_LOST_BITS   3    /* počet spodních bitů z 8bitového slova, které nejsou ukládány */
#define GREEN_LOST_BITS 2
#define BLUE_LOST_BITS  3
#define RED_MASK        0x1f /* 0001 1111 */
#define GREEN_MASK      0x3f /* 0011 1111 */
#define BLUE_MASK       0x1f /* 0001 1111 */

Samotné dekódování barev může vypadat následovně:

/* nejprve se prectou dva bajty z framebufferu */
unsigned char b1 = *(adr+1);
unsigned char b2 = *(adr);
 
/* posleze se prevedou na 16bitove slovo */
unsigned int  color = (b1 << 8) + b2;
 
/* a zase ziskame zpetnym prevodem hodnoty barvovych slozek */
/* 1) nejprve je 16bitove slovo posunuto doprava tak, aby se ve spodnich bitech nachazela prislusna slozka */
/* 2) posleze se provede maskovani spodnich peti ci sesti bitu, vysledkem je 16bitove slovo s nulovymi 10 ci 9 bity */
/* 3) nasledne se onech 5 ci 6 bitu posune doleva, aby vznikla osmibitova hodnota barvy */
unsigned char r = ((color >> RED_OFFSET)   & RED_MASK)   << RED_LOST_BITS;
unsigned char g = ((color >> GREEN_OFFSET) & GREEN_MASK) << GREEN_LOST_BITS;
unsigned char b = ((color >> BLUE_OFFSET)  & BLUE_MASK)  << BLUE_LOST_BITS;

Trojici výsledných bajtů uložíme do výstupního souboru jednoduše:

/* zapis barvovych slozek */
putc(r, fout);
putc(g, fout);
putc(b, fout);
adr+=2; /* posun na dalsi pixel ve framebufferu */

Výsledná podoba celé funkce provádějící export dat z framebufferu („screenshot“) může vypadat takto:

void saveFramebuffer(const char *filename, FramebufferInfo *framebufferInfoPtr, char *pixels)
{
    const int bpp = framebufferInfoPtr->bits_per_pixel;
    const int xres = framebufferInfoPtr->xres;
    const int yres = framebufferInfoPtr->yres;
 
    FILE *fout = fopen(filename, "wb");
    int i;
    char *adr=pixels;
 
    if (!fout) {
        perror("Unable to open output file");
        return;
    }
 
    /* hlavicka souboru s rastrovym obrazkem */
    fprintf(fout, "P6\n");
    fprintf(fout, "%d\n%d\n255\n", xres, yres);
 
    switch (bpp) {
        case 16: /* 16bitova barvova hloubka, predpokladejme format 565 */
            for (i=0; i<xres*yres; i++) {
                /* nejprve se prectou dva bajty z framebufferu */
                unsigned char b1 = *(adr+1);
                unsigned char b2 = *(adr);
                /* posleze se prevedou na 16bitove slovo */
                unsigned int  color = (b1 << 8) + b2;
                /* a zase ziskame zpetnym prevodem hodnoty barvovych slozek */
                unsigned char r = ((color >> RED_OFFSET)   & RED_MASK)   << RED_LOST_BITS;
                unsigned char g = ((color >> GREEN_OFFSET) & GREEN_MASK) << GREEN_LOST_BITS;
                unsigned char b = ((color >> BLUE_OFFSET)  & BLUE_MASK)  << BLUE_LOST_BITS;
                /* zapis barvovych slozek */
                putc(r, fout);
                putc(g, fout);
                putc(b, fout);
                adr+=2; /* posun na dalsi pixel ve framebufferu */
            }
            break;
        case 24: /* 24bitova barvova hloubka - lze nahradit jedinym zapisem, pokud nebudete prehazovat barvy! */
            for (i=0; i<xres*yres; i++) {
                fwrite(adr+2, 1, 1, fout);
                fwrite(adr+1, 1, 1, fout);
                fwrite(adr+0, 1, 1, fout);
                adr+=3; /* posun na dalsi pixel ve framebufferu */
            }
            break;
        case 32: /* 32bitova barvova hloubka */
            for (i=0; i<xres*yres; i++) {
                fwrite(adr+2, 1, 1, fout);
                fwrite(adr+1, 1, 1, fout);
                fwrite(adr+0, 1, 1, fout);
                adr+=4; /* posledni bajt se busi preskocit, neuklada se */
            }
            break;
    }
    fclose(fout);
}

7. Úplný zdrojový kód prvního demonstračního příkladu

Funkci pro export obsahu celého framebufferu do externího souboru typu PPM, jsem přidal do příkladu, s nímž jsme se již seznámili minule. Jde o příklad, který vykreslí několik úseček s různým sklonem a barvou. Výsledný screenshot po konverzi do PNG vypadá následovně:

Obrázek 1: Screenshot vygenerovaný příkladem rpi_fb11.c.

Úplný zdrojový kód tohoto příkladu je možné získat na adrese https://github.com/tisnik/pre­sentations/blob/master/rpi_fra­mebuffer/rpi_fb11.c, popř. alternativně i oblíbenou :-) metodou copy and paste:

/* Framebuffer na jednodeskovem mikropocitaci Raspberry Pi */
/* Autor: Pavel Tisnovsky, 2016 */
 
/* Demonstracni priklad cislo 11: vykreslovani usecek: rychlejsi varianta */
/*                                nepouzivajici funkci putpixel.          */
/*                                Pridani kodu pro ulozeni framebufferu   */
 
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include <linux/types.h>
 
 
 
#define ABS(x) ((x)<0 ? -(x) : (x))
 
/*
 * Datova struktura, do niz se ulozi informace o framebufferu.
 * Blizsi informace o teto strukture je mozne nalezt v hlavickovem souboru
 * dostupnem v adresari "/usr/include/linux/fb.h"
 */
typedef struct fb_var_screeninfo FramebufferInfo;
 
 
 
/*
 * Druha datova struktura popisujici zbyvajici vlastnosti framebufferu.
 * Blizsi informace o teto strukture je mozne nalezt v hlavickovem souboru
 * dostupnem v adresari "/usr/include/linux/fb.h"
 */
typedef struct fb_fix_screeninfo ModeInfo;
 
 
 
/*
 * Precteni vsech relevantnich informaci zjistenych o framebufferu. Pro korektni
 * funkci je zapotrebi, aby mel uzivatel pristup k zarizeni /dev/fb0
 * (postacuje byt ve skupine 'video' ci pouziti su/sudo)
 */
int readFramebufferInfo(int framebufferDevice,
                        FramebufferInfo *framebufferInfoPtr,
                        ModeInfo        *modeInfoPtr)
{
    /* Pokud operace ioctl probehne v poradku, vrati se 0 */
    if (ioctl(framebufferDevice, FBIOGET_VSCREENINFO, framebufferInfoPtr)) {
        perror("Nelze precist informace o framebufferu");
        return 0;
    }
 
    /* Pokud operace ioctl probehne v poradku, vrati se 0 */
    if (ioctl(framebufferDevice, FBIOGET_FSCREENINFO, modeInfoPtr)) {
        perror("Nelze precist informace o rezimu");
        return 0;
    }
    return 1;
}
 
 
 
/*
 * Funkce line() platna pro nezname graficke rezimy.
 */
void lineNull(const int x1, const int y1, const int x2, const int y2,
              const unsigned char r, const unsigned char g, const unsigned char b,
              char *pixels, const int line_length)
{
}
 
 
 
/*
 * Funkce line platna pouze pro graficke rezimy true-color
 * s formatem 8-8-8-8 (popr. muze byt alfa kanal ignorovan).
 * Funkcni naproklad pro graficke karty Intel.
 */
void lineBGRA(const int x1, const int y1, const int x2, const int y2,
              const unsigned char r, const unsigned char g, const unsigned char b,
              char *pixels, const int line_length)
{
    /* vypocet adresy zapisu dat */
    /* pocitame v 32bitovych slovech, tj. line_length je nutne podelit ctyrmi */
    unsigned int index = x1 + (y1*line_length >> 2);
    /* >> 2 nahrazuje deleni ctyrmi */
 
    /* vlastni provedeni vykresleni usecky */
    int x = x1;
    int y = y1;
    /* zrcadleni algoritmu pro dalsi oktanty */
    int dx = ABS(x2-x1), sx = x1<x2 ? 1 : -1;
    int dy = ABS(y2-y1), sy = y1<y2 ? 1 : -1;
    int err = (dx>dy ? dx : -dy)/2, e2;
 
    /* uplna barva v jednom slove */
    __u32 color = (r<<16) | (g<<8) | b;
    __u32 *pixels32 = (__u32*)pixels;
 
    /* pri posunu po x-ove ose se index musi zvysit ci snizit o 4 (bajty) tj. o 1 32-bitove slovo */
    int offsetX = x1<x2 ? 1: -1;
    /* pri posunu po y-ove ose se index musi zvysit ci snizit o delku radku (ve slovech) */
    int offsetY = y1<y2 ? (line_length >> 2) : - (line_length >> 2);
 
    while (1) {
        /* vlastni provedeni zapisu barvy pixelu */
        *(pixels32+index) = color;
 
        if (x==x2 && y==y2) {
            break;
        }
        e2 = err;
        if (e2 >-dx) {
            /* prepocet kumulovane chyby */
            err -= dy;
            /* posun na predchozi ci dalsi pixel na radku */
            x += sx;
            index += offsetX;
        }
        if (e2 < dy) {
            /* prepocet kumulovane chyby */
            err += dx;
            /* posun na predchozi ci nasledujici radek */
            y += sy;
            index += offsetY;
        }
    }
}
 
 
 
/*
 * Funkce line platna pouze pro graficke rezimy true-color
 * s formatem 8-8-8-8 (popr. muze byt alfa kanal ignorovan).
 * Funkcni pro Raspberry Pi s poradim bajtu R,G,B,A.
 */
void lineRGBA(const int x1, const int y1, const int x2, const int y2,
              const unsigned char r, const unsigned char g, const unsigned char b,
              char *pixels, const int line_length)
{
    lineBGRA(x1, y1, x2, y2, b, g, r, pixels, line_length);
}
 
 
 
/*
 * Plati pro format 565
 */
#define RED_OFFSET     11
#define GREEN_OFFSET    5
#define BLUE_OFFSET     0
#define RED_LOST_BITS   3
#define GREEN_LOST_BITS 2
#define BLUE_LOST_BITS  3
#define RED_MASK        0x1f /* 0001 1111 */
#define GREEN_MASK      0x3f /* 0011 1111 */
#define BLUE_MASK       0x1f /* 0001 1111 */
 
 
 
/*
 * Funkce line platna pouze pro graficke rezimy hi-color
 * s formatem 5-6-5.
 */
void line565(const int x1, const int y1, const int x2, const int y2,
             const unsigned char r, const unsigned char g, const unsigned char b,
             char *pixels, const int line_length)
{
    /* vypocet barvy pixelu, v zavorce nejdrive snizime bitovou sirku
     * rezervovanou pro jednotlive barvove slozky a posleze bity, ktere
     * reprezentuji barvovou slozku posuneme do spravne pozice ve slove */
    unsigned int pixel_value = (r >> RED_LOST_BITS)   << RED_OFFSET |
                               (g >> GREEN_LOST_BITS) << GREEN_OFFSET |
                               (b >> BLUE_LOST_BITS)  << BLUE_OFFSET;
 
    /* prevod na dvojici bajtu */
    unsigned char byte1 = pixel_value & 0xff;
    unsigned char byte2 = pixel_value >> 8;
 
    /* vypocet adresy zapisu dat */
    unsigned int index = (x1<<1) + y1*line_length;
    /* << 1 nahrazuje nasobeni dvema */
 
    /* vykresleni usecky */
    int x = x1;
    int y = y1;
    /* zrcadleni algoritmu pro dalsi oktanty */
    int dx = ABS(x2-x1), sx = x1<x2 ? 1 : -1;
    int dy = ABS(y2-y1), sy = y1<y2 ? 1 : -1;
    int err = (dx>dy ? dx : -dy)/2, e2;
 
    /* pri posunu po x-ove ose se index musi zvysit ci snizit o 2 (bajty) */
    int offsetX = x1<x2 ? 2: -2;
    /* pri posunu po y-ove ose se index musi zvysit ci snizit o delku radku (v bajtech) */
    int offsetY = y1<y2 ? line_length : - line_length;
 
    while (1) {
        /* vlastni provedeni zapisu barvy pixelu */
        *(pixels+index) = byte1;
        *(pixels+index+1) = byte2;
 
        if (x==x2 && y==y2) {
            break;
        }
        e2 = err;
        if (e2 >-dx) {
            /* prepocet kumulovane chyby */
            err -= dy;
            /* posun na predchozi ci dalsi pixel na radku */
            x += sx;
            index += offsetX;
        }
        if (e2 < dy) {
            /* prepocet kumulovane chyby */
            err += dx;
            /* posun na predchozi ci nasledujici radek */
            y += sy;
            index += offsetY;
        }
    }
}
 
 
 
/*
 * Novy datovy typ - ukazatel na (libovolnou) funkci line.
 */
typedef void (*LineFunction)(const int, const int, const int, const int,
                             const unsigned char, const unsigned char, const unsigned char,
                             char*, const int);
 
 
 
/*
 * Funkce, ktera vraci korektni funkci pro operaci line().
 */
LineFunction getProperLineFunction(int bits_per_pixel, int type, int visual, int redOffset)
{
    /* umime rozeznat pouze format bez bitovych rovin a bez palety */
    if (type == FB_TYPE_PACKED_PIXELS && visual == FB_VISUAL_TRUECOLOR) {
        if (bits_per_pixel == 16) {
            return line565;
        }
        if (bits_per_pixel == 32) {
            if (redOffset == 16) {
                return lineBGRA;
            }
            else {
                return lineRGBA;
            }
        }
    }
    return lineNull;
}
 
 
 
/*
 * Ulozeni obsahu framebufferu do souboru s rastrovym obrazkem.
 */
void saveFramebuffer(const char *filename, FramebufferInfo *framebufferInfoPtr, char *pixels)
{
    const int bpp = framebufferInfoPtr->bits_per_pixel;
    const int xres = framebufferInfoPtr->xres;
    const int yres = framebufferInfoPtr->yres;
 
    FILE *fout = fopen(filename, "wb");
    int i;
    char *adr=pixels;
 
    if (!fout) {
        perror("Unable to open output file");
        return;
    }
 
    /* hlavicka souboru s rastrovym obrazkem */
    fprintf(fout, "P6\n");
    fprintf(fout, "%d\n%d\n255\n", xres, yres);
 
    switch (bpp) {
        case 16: /* 16bitova barvova hloubka, predpokladejme format 565 */
            for (i=0; i<xres*yres; i++) {
                /* nejprve se prectou dva bajty z framebufferu */
                unsigned char b1 = *(adr+1);
                unsigned char b2 = *(adr);
                /* posleze se prevedou na 16bitove slovo */
                unsigned int  color = (b1 << 8) + b2;
                /* a zase ziskame zpetnym prevodem hodnoty barvovych slozek */
                unsigned char r = ((color >> RED_OFFSET)   & RED_MASK)   << RED_LOST_BITS;
                unsigned char g = ((color >> GREEN_OFFSET) & GREEN_MASK) << GREEN_LOST_BITS;
                unsigned char b = ((color >> BLUE_OFFSET)  & BLUE_MASK)  << BLUE_LOST_BITS;
                /* zapis barvovych slozek */
                putc(r, fout);
                putc(g, fout);
                putc(b, fout);
                adr+=2; /* posun na dalsi pixel ve framebufferu */
            }
            break;
        case 24: /* 24bitova barvova hloubka - lze nahradit jedinym zapisem, pokud nebudete prehazovat barvy! */
            for (i=0; i<xres*yres; i++) {
                fwrite(adr+2, 1, 1, fout);
                fwrite(adr+1, 1, 1, fout);
                fwrite(adr+0, 1, 1, fout);
                adr+=3; /* posun na dalsi pixel ve framebufferu */
            }
            break;
        case 32: /* 32bitova barvova hloubka */
            for (i=0; i<xres*yres; i++) {
                fwrite(adr+2, 1, 1, fout);
                fwrite(adr+1, 1, 1, fout);
                fwrite(adr+0, 1, 1, fout);
                adr+=4; /* posledni bajt se busi preskocit, neuklada se */
            }
            break;
    }
    fclose(fout);
}
 
 
 
/*
 * Vykresleni testovaciho obrazku s vyuzitim funkce line.
 */
void drawTestImage(int framebufferDevice,
                   FramebufferInfo *framebufferInfoPtr,
                   ModeInfo        *modeInfoPtr)
{
#define OFFSET 300
    /* casto pouzivane konstanty */
    const int buffer_length = modeInfoPtr->smem_len;
    const int pitch = modeInfoPtr->line_length;
 
    /* ziskame spravnou verzi funkce line */
    LineFunction line = getProperLineFunction(framebufferInfoPtr->bits_per_pixel,
                                              modeInfoPtr->type,
                                              modeInfoPtr->visual,
                                              framebufferInfoPtr->red.offset);
 
    /* ziskat primy pristup do framebufferu */
    char *pixels = (char*)mmap(0, buffer_length,
                               PROT_READ | PROT_WRITE,
                               MAP_SHARED, framebufferDevice,
                               0);
 
    if (pixels != MAP_FAILED) {
        int i;
        int r, g, b;
        /* nejprve vymazeme cely framebuffer */
        memset(pixels, 0, buffer_length);
 
        /* vykreslime nekolik usecek s ruznym sklonem a barvou*/
        for (i=0; i<256; i++) {
            r = i;
            g = i;
            b = 255-i;
            line(i*3, 0, i*3, 100, r, g, b, pixels, pitch);
            r = 255;
            g = i;
            b = 255-i;
            line(i*4, 150, i*5, 250, r, g, b, pixels, pitch);
        }
        for (i=0; i<=300; i+=10) {
            line(0, 300 + i, i, 300 + 300, 255, 255, 255, pixels, pitch);
 
            line(300, 300, 600, 300 + i, 128, 128, 255, pixels, pitch);
        }
        /* ulozeni framebufferu */
        saveFramebuffer("rpi_fb11.ppm", framebufferInfoPtr, pixels);
        /* cekani na stisk klavesy */
        getchar();
        munmap(pixels, buffer_length);
    }
    else {
        perror("Nelze pristupovat k framebufferu");
    }
}
 
 
 
/* Vstupni bod do demonstracniho prikladu... :) */
int main(int argc, char **argv)
{
    FramebufferInfo framebufferInfo;
    ModeInfo        modeInfo;
    int framebufferDevice = 0;
 
    /* Ze zarizeni potrebujeme cist i zapisovat.*/
    framebufferDevice = open("/dev/fb0", O_RDWR);
 
    /* Pokud otevreni probehlo uspesne, nacteme
     * a nasledne vypiseme informaci o framebufferu.*/
    if (framebufferDevice != -1) {
        /* Precteni informaci o framebufferu a test, zda se vse podarilo */
        if (readFramebufferInfo(framebufferDevice, &framebufferInfo, &modeInfo)) {
            drawTestImage(framebufferDevice, &framebufferInfo, &modeInfo);
        }
        close(framebufferDevice);
        return 0;
    }
    /* Otevreni se nezadarilo, vypiseme tudiz pouze chybove hlaseni.*/
    else {
        perror("Nelze otevrit ovladac /dev/fb0");
        return 1;
    }
}
 
 
 
/* finito */

8. Vykreslování úseček s aplikací antialiasingu

Minule popsaný Bresenhamův algoritmus použitý při vykreslování úseček je velmi rychlý, pro jeho implementaci lze použít pouze celočíselnou aritmetiku a navíc ho je možné různými způsoby optimalizovat, aby se omezil počet podmíněných skoků. Ovšem za tuto vysokou efektivitu musíme zaplatit: Bresenhamův algoritmus ve své základní variantě nedokáže vyhlazovat „schody“, které vznikají na prakticky všech úsečkách, samozřejmě kromě úseček přísně svislých či vodorovných. To sice nevadí ve chvíli, kdy vykreslovací zařízení má vysoké rozlišení a navíc nedokáže pracovat s různými intenzitami barev (příkladem mohou být laserové tiskárny a osvitové jednotky). Ovšem na monitorech, které mají v porovnání se zmíněnými osvitovými jednotkami řádově menší rozlišení, je někdy vhodnější použít jiný algoritmus.

Obrázek 2: Krásné vodorovné úsečky – nebo ne?

Podívali jste se pozorně na předchozí obrázek? Zdánlivě jsou na něm vykresleny vodorovné čáry, ovšem skutečnost je jiná. Jde o úsečky, jejichž druhý vrchol je posunutý přesně o jeden pixel dolů, takže při použití klasického Bresenhamova algoritmu dostaneme tento obrázek, kde je každý „schod“ uprostřed úseček jasně patrný:

Obrázek 3: „Schody“ vytvořené Bresenhamovým algoritmem.

A právě na rozdílech mezi druhým a třetím obrázkem je postaven algoritmus, jehož autorem je Xiaolin Wu. Tento algoritmus vykresluje každou úsečku se šířkou dvou pixelů, ovšem takovým způsobem, že součet intenzit těchto dvou pixelů se rovná původní barvě úsečky, takže dochází k optickému klamu a zdánlivému zmizení oněch schodů. Nevýhodou je složitější výpočet, použití operací s plovoucí řádovou čárkou (popř. fixed point) a nutnost gamma korekce (tu jsem provedl až ve fázi postprocessingu). Celý algoritmus v mé úpravě vypadá následovně:

/*
 * Funkce pro vykresleni usecky s aplikaci antialiasingu.
 */
void lineAA(const int x1, const int y1, const int x2, const int y2,
          char *pixels, const int line_length, PutpixelFunction putpixel)
{
    /* specialni pripad - svisla usecka */
    if (x1==x2) {
        verticalLine(x1, y1, y2, pixels, line_length, putpixel);
        return;
    }
 
    /* specialni pripad - vodorovna usecka */
    if (y1==y2) {
        horizontalLine(x1, x2, y1, pixels, line_length, putpixel);
        return;
    }
 
    /* mame smulu a musime pouzit plnou verzi algoritmu */
 
    /* konstanty pouzite pri vykreslovani */
    int dx = x2 - x1;
    int dy = y2 - y1;
    double s, p, e=255.0;
    int x, y, xdelta, ydelta, xpdelta, ypdelta, xp, yp;
    int i, imin, imax;
 
    /* pomocne promenne - pocatecni a koncove body */
    int xx1 = x1 < x2 ? x1 : x2;
    int xx2 = x1 < x2 ? x2 : x1;
    int yy1 = x1 < x2 ? y1 : y2;
    int yy2 = x1 < x2 ? y2 : y1;
 
    /* nastaveni pro sklony mensi nez 45 stupnu */
    if (ABS(dx) > ABS(dy)) {
        s=(double)dy/(double)dx;
        imin=xx1;  imax=xx2;
        x=xx1;     y=yy1;
        xdelta=1;  ydelta=0;
        xpdelta=0;
        xp=0;
        if (yy2>yy1) {
            ypdelta=1;
            yp=1;
        }
        else {
            s=-s;
            ypdelta=-1;
            yp=-1;
        }
    }
    /* nastaveni pro sklony vetsi nez 45 stupnu */
    else {
        s=(double)dx/(double)dy;
        xdelta=0; ydelta=1;
        ypdelta=0;
        yp=0;
        if (yy2>yy1) {
            imin=yy1;    imax=yy2;
            x=xx1;       y=yy1;
            xpdelta=1;
            xp=1;
        }
        else {
            s=-s;
            imin=yy2;    imax=yy1;
            x=xx2;       y=yy2;
            xpdelta=-1;
            xp=-1;
        }
    }
    /* vlastni vykreslovaci smycka (zde bez optimalizaci!) */
    p=s*256.0;
    for (i=imin; i<=imax; i++) {
        int c1 = (int)e;
        int c2 = 255-c1;
        putpixel(x+xp, y+yp, c2, c2, c2, pixels, line_length);
        putpixel(x, y, c1, c1, c1, pixels, line_length);
        e=e-p;
        x+=xdelta;
        y+=ydelta;
        if (e<0.0) {
            e+=256.0;
            x+=xpdelta;
            y+=ypdelta;
        }
    }
}

Obrázek 4: Bližší pohled na výsledek Wuuva algoritmu. Každá úsečka má šířku dvou pixelů s průběžnou změnou barvy (a průhlednosti).

9. Úplný zdrojový kód druhého demonstračního příkladu

Funkci pro vykreslení úsečky s aplikací antialiasingu přidáme do demonstračního příkladu, v němž se vykreslí několik téměř vodorovných úseček. V první polovině framebufferu je použit Bresenhamův algoritmus, ve druhé polovině pak pomalejší, ale kvalitnější algoritmus vymyšlený Xiaolin Wuem. Příklad je kvůli zjednodušení upraven takovým způsobem, že pixely mohou být pouze monochromatické. Výsledek bude vypadat následovně:

Obrázek 5: Screenshot vygenerovaný příkladem rpi_fb12.c.

Úplný zdrojový kód příkladu, který po svém spuštění vykreslí několik úseček bez antialiasingu a s aplikovaným antialiasingem, je možné získat na adrese https://github.com/tisnik/pre­sentations/blob/master/rpi_fra­mebuffer/rpi_fb12.c. Navíc je výpis tohoto příkladu zobrazen i pod tímto odstavcem:

/* Framebuffer na jednodeskovem mikropocitaci Raspberry Pi */
/* Autor: Pavel Tisnovsky, 2016 */
 
/* Demonstracni priklad cislo 12: vykreslovani usecek s antialiasingem. */
/*                                (neoptimalizovana varianta) */
 
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/fb.h>
 
 
 
/*
 * Datova struktura, do niz se ulozi informace o framebufferu.
 * Blizsi informace o teto strukture je mozne nalezt v hlavickovem souboru
 * dostupnem v adresari "/usr/include/linux/fb.h"
 */
typedef struct fb_var_screeninfo FramebufferInfo;
 
 
 
/*
 * Druha datova struktura popisujici zbyvajici vlastnosti framebufferu.
 * Blizsi informace o teto strukture je mozne nalezt v hlavickovem souboru
 * dostupnem v adresari "/usr/include/linux/fb.h"
 */
typedef struct fb_fix_screeninfo ModeInfo;
 
 
 
/*
 * Precteni vsech relevantnich informaci zjistenych o framebufferu. Pro korektni
 * funkci je zapotrebi, aby mel uzivatel pristup k zarizeni /dev/fb0
 * (postacuje byt ve skupine 'video' ci pouziti su/sudo)
 */
int readFramebufferInfo(int framebufferDevice,
                        FramebufferInfo *framebufferInfoPtr,
                        ModeInfo        *modeInfoPtr)
{
    /* Pokud operace ioctl probehne v poradku, vrati se 0 */
    if (ioctl(framebufferDevice, FBIOGET_VSCREENINFO, framebufferInfoPtr)) {
        perror("Nelze precist informace o framebufferu");
        return 0;
    }
 
    /* Pokud operace ioctl probehne v poradku, vrati se 0 */
    if (ioctl(framebufferDevice, FBIOGET_FSCREENINFO, modeInfoPtr)) {
        perror("Nelze precist informace o rezimu");
        return 0;
    }
    return 1;
}
 
 
 
/*
 * Funkce putpixel platna pro nezname graficke rezimy.
 */
void putpixelNull(const int x, const int y,
                 const char r, const char g, const char b,
                 char *pixels, const int line_length)
{
}
 
 
 
/*
 * Funkce putpixel platna pouze pro graficke rezimy true-color
 * s formatem 8-8-8-8 (popr. muze byt alfa kanal ignorovan).
 * Funkcni napriklad pro graficke karty Intel.
 */
void putpixelBGRA(const int x, const int y,
                 const char r, const char g, const char b,
                 char *pixels, const int line_length)
{
    /* vypocet adresy zapisu dat */
    unsigned int index = (x<<2) + y*line_length;
    /* << 2 nahrazuje nasobeni ctyrmi */
 
    /* vlastni provedeni zapisu */
    *(pixels+index) = b;
    index++;
    *(pixels+index) = g;
    index++;
    *(pixels+index) = r;
}
 
 
 
/*
 * Funkce putpixel platna pouze pro graficke rezimy true-color
 * s formatem 8-8-8-8 (popr. muze byt alfa kanal ignorovan).
 * Funkcni pro Raspberry Pi s poradim bajtu R,G,B,A.
 */
void putpixelRGBA(const int x, const int y,
                 const char r, const char g, const char b,
                 char *pixels, const int line_length)
{
    putpixelBGRA(x, y, b, g, r, pixels, line_length);
}
 
 
 
/*
 * Plati pro format 565
 */
#define RED_OFFSET     11
#define GREEN_OFFSET    5
#define BLUE_OFFSET     0
#define RED_LOST_BITS   3
#define GREEN_LOST_BITS 2
#define BLUE_LOST_BITS  3
#define RED_MASK        0x1f /* 0001 1111 */
#define GREEN_MASK      0x3f /* 0011 1111 */
#define BLUE_MASK       0x1f /* 0001 1111 */
 
 
 
/*
 * Funkce putpixel platna pouze pro graficke rezimy hi-color
 * s formatem 5-6-5.
 */
void putpixel565(const int x, const int y,
                 const char r, const char g, const char b,
                 char *pixels, const int line_length)
{
    /* vypocet barvy pixelu, v zavorce nejdrive snizime bitovou sirku
     * rezervovanou pro jednotlive barvove slozky a posleze bity, ktere
     * reprezentuji barvovou slozku posuneme do spravne pozice ve slove */
    unsigned int pixel_value = (r >> RED_LOST_BITS)   << RED_OFFSET |
                               (g >> GREEN_LOST_BITS) << GREEN_OFFSET |
                               (b >> BLUE_LOST_BITS)  << BLUE_OFFSET;
 
    /* prevod na dvojici bajtu */
    unsigned char byte1 = pixel_value & 0xff;
    unsigned char byte2 = pixel_value >> 8;
 
    /* vypocet adresy zapisu dat */
    unsigned int index = (x<<1) + y*line_length;
    /* << 1 nahrazuje nasobeni dvema */
 
    /* vlastni provedeni zapisu */
    *(pixels+index) = byte1;
    index++;
    *(pixels+index) = byte2;
}
 
 
 
/*
 * Novy datovy typ - ukazatel na (libovolnou) funkci putpixel.
 */
typedef void (*PutpixelFunction)(const int, const int,
                                 const char, const char, const char,
                                 char*, const int);
 
 
 
/*
 * Funkce, ktera vraci korektni funkci pro operaci putpixel().
 */
PutpixelFunction getProperPutpixelFunction(int bits_per_pixel, int type, int visual, int redOffset)
{
    /* umime rozeznat pouze format bez bitovych rovin a bez palety */
    if (type == FB_TYPE_PACKED_PIXELS && visual == FB_VISUAL_TRUECOLOR) {
        /* framebuffer s bitovou hloubkou 16bpp */
        if (bits_per_pixel == 16) {
            return putpixel565;
        }
        /* framebuffery s bitovou hloubkou 32bpp */
        if (bits_per_pixel == 32) {
            /* rozlisujeme podle pozice cervene slozky */
            if (redOffset == 16) {
                return putpixelBGRA;
            }
            else {
                return putpixelRGBA;
            }
        }
    }
    return putpixelNull;
}
 
 
 
/*
 * Nekolik maker pouzitych v algoritmech pro vykreslovani usecek.
 */
#define ABS(x) ((x)<0 ? -(x) : (x))
#define MAX(a,b) ((a)>(b) ? (a) : (b))
#define MIN(a,b) ((a)<(b) ? (a) : (b))
 
 
 
/*
 * Specialni pripad: vodorovna usecka.
 */
void horizontalLine(const int x1, const int x2, const int y,
                    char *pixels, const int line_length, PutpixelFunction putpixel)
{
    int x;
    int from_x = MIN(x1, x2);
    int to_x = MAX(x1, x2);
    for (x = from_x; x <= to_x; x++) {
        putpixel(x, y, 0xff, 0xff, 0xff, pixels, line_length);
    }
}
 
 
 
/*
 * Specialni pripad: svisla usecka.
 */
void verticalLine(const int x, const int y1, const int y2,
                  char *pixels, const int line_length, PutpixelFunction putpixel)
{
    int y;
    int from_y = MIN(y1, y2);
    int to_y = MAX(y1, y2);
    for (y = from_y; y <= to_y; y++) {
        putpixel(x, y, 0xff, 0xff, 0xff, pixels, line_length);
    }
}
 
 
 
/*
 * Funkce pro vykresleni usecky Bresenhamovym algoritmem.
 * (tj. bez antialiasingu)
 */
void line(const int x1, const int y1, const int x2, const int y2,
          char *pixels, const int line_length, PutpixelFunction putpixel)
{
    /* specialni pripad - svisla usecka */
    if (x1==x2) {
        verticalLine(x1, y1, y2, pixels, line_length, putpixel);
        return;
    }
 
    /* specialni pripad - vodorovna usecka */
    if (y1==y2) {
        horizontalLine(x1, x2, y1, pixels, line_length, putpixel);
        return;
    }
 
    /* mame smulu a musime pouzit plnou verzi algoritmu */
 
    /* zrcadleni algoritmu pro dalsi oktanty */
    int x = x1;
    int y = y1;
 
    /* konstanty pouzite pri vykreslovani */
    int dx = ABS(x2-x1), sx = x1<x2 ? 1 : -1;
    int dy = ABS(y2-y1), sy = y1<y2 ? 1 : -1;
    int err = (dx>dy ? dx : -dy)/2, e2;
 
    while (1) {
        putpixel(x, y, 0xff, 0xff, 0xff, pixels, line_length);
        /* test, zda se jiz doslo k poslednimu bodu */
        if (x==x2 && y==y2) {
            break;
        }
        e2 = err;
        if (e2 >-dx) {
            /* prepocet kumulovane chyby */
            err -= dy;
            /* posun na predchozi ci dalsi pixel na radku */
            x += sx;
        }
        if (e2 < dy) {
            /* prepocet kumulovane chyby */
             err += dx;
             /* posun na predchozi ci nasledujici radek */
             y += sy;
        }
    }
}
 
 
 
/*
 * Funkce pro vykresleni usecky s aplikaci antialiasingu.
 */
void lineAA(const int x1, const int y1, const int x2, const int y2,
          char *pixels, const int line_length, PutpixelFunction putpixel)
{
    /* specialni pripad - svisla usecka */
    if (x1==x2) {
        verticalLine(x1, y1, y2, pixels, line_length, putpixel);
        return;
    }
 
    /* specialni pripad - vodorovna usecka */
    if (y1==y2) {
        horizontalLine(x1, x2, y1, pixels, line_length, putpixel);
        return;
    }
 
    /* mame smulu a musime pouzit plnou verzi algoritmu */
 
    /* konstanty pouzite pri vykreslovani */
    int dx = x2 - x1;
    int dy = y2 - y1;
    double s, p, e=255.0;
    int x, y, xdelta, ydelta, xpdelta, ypdelta, xp, yp;
    int i, imin, imax;
 
    /* pomocne promenne - pocatecni a koncove body */
    int xx1 = x1 < x2 ? x1 : x2;
    int xx2 = x1 < x2 ? x2 : x1;
    int yy1 = x1 < x2 ? y1 : y2;
    int yy2 = x1 < x2 ? y2 : y1;
 
    /* nastaveni pro sklony mensi nez 45 stupnu */
    if (ABS(dx) > ABS(dy)) {
        s=(double)dy/(double)dx;
        imin=xx1;  imax=xx2;
        x=xx1;     y=yy1;
        xdelta=1;  ydelta=0;
        xpdelta=0;
        xp=0;
        if (yy2>yy1) {
            ypdelta=1;
            yp=1;
        }
        else {
            s=-s;
            ypdelta=-1;
            yp=-1;
        }
    }
    /* nastaveni pro sklony vetsi nez 45 stupnu */
    else {
        s=(double)dx/(double)dy;
        xdelta=0; ydelta=1;
        ypdelta=0;
        yp=0;
        if (yy2>yy1) {
            imin=yy1;    imax=yy2;
            x=xx1;       y=yy1;
            xpdelta=1;
            xp=1;
        }
        else {
            s=-s;
            imin=yy2;    imax=yy1;
            x=xx2;       y=yy2;
            xpdelta=-1;
            xp=-1;
        }
    }
    /* vlastni vykreslovaci smycka (zde bez optimalizaci!) */
    p=s*256.0;
    for (i=imin; i<=imax; i++) {
        int c1 = (int)e;
        int c2 = 255-c1;
        putpixel(x+xp, y+yp, c2, c2, c2, pixels, line_length);
        putpixel(x, y, c1, c1, c1, pixels, line_length);
        e=e-p;
        x+=xdelta;
        y+=ydelta;
        if (e<0.0) {
            e+=256.0;
            x+=xpdelta;
            y+=ypdelta;
        }
    }
}
 
 
 
/*
 * Ulozeni obsahu framebufferu do souboru s rastrovym obrazkem.
 */
void saveFramebuffer(const char *filename, FramebufferInfo *framebufferInfoPtr, char *pixels)
{
    const int bpp = framebufferInfoPtr->bits_per_pixel;
    const int xres = framebufferInfoPtr->xres;
    const int yres = framebufferInfoPtr->yres;
 
    FILE *fout = fopen(filename, "wb");
    int i;
    char *adr=pixels;
 
    if (!fout) {
        perror("Unable to open output file");
        return;
    }
 
    /* hlavicka souboru s rastrovym obrazkem */
    fprintf(fout, "P6\n");
    fprintf(fout, "%d\n%d\n255\n", xres, yres);
 
    switch (bpp) {
        case 16: /* 16bitova barvova hloubka, predpokladejme format 565 */
            for (i=0; i<xres*yres; i++) {
                /* nejprve se prectou dva bajty z framebufferu */
                unsigned char b1 = *(adr+1);
                unsigned char b2 = *(adr);
                /* posleze se prevedou na 16bitove slovo */
                unsigned int  color = (b1 << 8) + b2;
                /* a zase ziskame zpetnym prevodem hodnoty barvovych slozek */
                unsigned char r = ((color >> RED_OFFSET)   & RED_MASK)   << RED_LOST_BITS;
                unsigned char g = ((color >> GREEN_OFFSET) & GREEN_MASK) << GREEN_LOST_BITS;
                unsigned char b = ((color >> BLUE_OFFSET)  & BLUE_MASK)  << BLUE_LOST_BITS;
                /* zapis barvovych slozek */
                putc(r, fout);
                putc(g, fout);
                putc(b, fout);
                adr+=2; /* posun na dalsi pixel ve framebufferu */
            }
            break;
        case 24: /* 24bitova barvova hloubka - lze nahradit jedinym zapisem, pokud nebudete prehazovat barvy! */
            for (i=0; i<xres*yres; i++) {
                fwrite(adr+2, 1, 1, fout);
                fwrite(adr+1, 1, 1, fout);
                fwrite(adr+0, 1, 1, fout);
                adr+=3; /* posun na dalsi pixel ve framebufferu */
            }
            break;
        case 32: /* 32bitova barvova hloubka */
            for (i=0; i<xres*yres; i++) {
                fwrite(adr+2, 1, 1, fout);
                fwrite(adr+1, 1, 1, fout);
                fwrite(adr+0, 1, 1, fout);
                adr+=4; /* posledni bajt se busi preskocit, neuklada se */
            }
            break;
    }
    fclose(fout);
}
 
 
 
/*
 * Vykresleni testovaciho obrazku s vyuzitim funkci line a putpixel.
 */
void drawTestImage(int framebufferDevice,
                   FramebufferInfo *framebufferInfoPtr,
                   ModeInfo        *modeInfoPtr)
{
#define OFFSET 300
    /* casto pouzivane konstanty */
    const int buffer_length = modeInfoPtr->smem_len;
    const int pitch = modeInfoPtr->line_length;
 
    /* ziskame spravnou verzi funkce putpixel */
    PutpixelFunction putpixel = getProperPutpixelFunction(framebufferInfoPtr->bits_per_pixel,
                                                          modeInfoPtr->type,
                                                          modeInfoPtr->visual,
                                                          framebufferInfoPtr->red.offset);
 
    /* ziskat primy pristup do framebufferu */
    char *pixels = (char*)mmap(0, buffer_length,
                               PROT_READ | PROT_WRITE,
                               MAP_SHARED, framebufferDevice,
                               0);
 
    if (pixels != MAP_FAILED) {
        int i;
        /* nejprve vymazeme cely framebuffer */
        memset(pixels, 0, buffer_length);
 
        /* vykreslime nekolik usecek s ruznym sklonem (vzdy bude mit jeden "schod") */
        for (i=0; i<=350; i+=10) {
            /* bez antialiasingu */
            line(0, i, i*2, i+1, pixels, pitch, putpixel);
            /* s antialiasingem */
            lineAA(0, 400+i, i*2, 400+1+i, pixels, pitch, putpixel);
        }
        /* ulozeni framebufferu */
        saveFramebuffer("rpi_fb12.ppm", framebufferInfoPtr, pixels);
        /* cekani na stisk klavesy */
        getchar();
        munmap(pixels, buffer_length);
    }
    else {
        perror("Nelze pristupovat k framebufferu");
    }
}
 
 
 
/* Vstupni bod do demonstracniho prikladu... :) */
int main(int argc, char **argv)
{
    FramebufferInfo framebufferInfo;
    ModeInfo        modeInfo;
    int framebufferDevice = 0;
 
    /* Ze zarizeni potrebujeme cist i zapisovat.*/
    framebufferDevice = open("/dev/fb0", O_RDWR);
 
    /* Pokud otevreni probehlo uspesne, nacteme
     * a nasledne vypiseme informaci o framebufferu.*/
    if (framebufferDevice != -1) {
        /* Precteni informaci o framebufferu a test, zda se vse podarilo */
        if (readFramebufferInfo(framebufferDevice, &framebufferInfo, &modeInfo)) {
            drawTestImage(framebufferDevice, &framebufferInfo, &modeInfo);
        }
        close(framebufferDevice);
        return 0;
    }
    /* Otevreni se nezadarilo, vypiseme tudiz pouze chybove hlaseni.*/
    else {
        perror("Nelze otevrit ovladac /dev/fb0");
        return 1;
    }
}
 
 
 
/* finito */

10. Třešnička na závěr: non-photorealistic rendering

Ve chvíli, kdy již umíme vykreslovat úsečky s vyhlazenými hranami, je možné se přesunout dále, například do oblasti takzvaných NPR (Non-Photorealistic Rendering) algoritmů. Dnes si ukážeme pouze jediný algoritmus, který jsem již popsal v článcích Metoda přesouvání prostředního bodu a obrázky plasmy a Trojrozměrné modely terénu . Jedná se tzv. midpoint algoritmus, který se vhodně nastavenými parametry dokáže napodobit roztřesené čáry kreslené rukou. Použití může být rozmanité, od pseudo architektonických 3D výkresů až po alternativněji pojaté počítačové hry. Algoritmus je vlastně velmi jednoduchý a pracuje rekurzivně:

  1. Vstupem jsou dva koncové body úsečky.
  2. Vypočte se prostřední bod na úsečce (midpoint).
  3. Vypočte se normálový vektor v tomto bodě.
  4. Bod se posune ve směru (či proti směru) normálového vektoru o náhodnou hodnotu.
  5. Rekurzivní volání pro první koncový bod o posunutý midpoint.
  6. Rekurzivní volání pro druhý koncový bod o posunutý midpoint.
  7. Ve chvíli, kdy je vzdálenost bodů menší, než určitá konstanta, se vykreslí úsečka.

Obrázek 6: Několik úseček vygenerovaných algoritmem popsaným v této kapitole.

11. Úplný zdrojový kód třetího demonstračního příkladu

Podívejme se nyní na úplný zdrojový kód dnešního třetího demonstračního příkladu. Pro korektní slinkování výsledného spustitelného souboru je nutné použít volbu -lm, která zajistí přidání matematických funkcí použitých jak pro vytváření obrazců, tak i pro normalizaci vektorů. Zdrojový kód tohoto demonstračního příkladu je dostupný na adrese https://github.com/tisnik/pre­sentations/blob/master/rpi_fra­mebuffer/rpi_fb13.c:

/* Framebuffer na jednodeskovem mikropocitaci Raspberry Pi */
/* Autor: Pavel Tisnovsky, 2016 */
 
/* Demonstracni priklad cislo 13: non photorealistic rendering. */
 
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include <math.h>
 
 
 
/*
 * Datova struktura, do niz se ulozi informace o framebufferu.
 * Blizsi informace o teto strukture je mozne nalezt v hlavickovem souboru
 * dostupnem v adresari "/usr/include/linux/fb.h"
 */
typedef struct fb_var_screeninfo FramebufferInfo;
 
 
 
/*
 * Druha datova struktura popisujici zbyvajici vlastnosti framebufferu.
 * Blizsi informace o teto strukture je mozne nalezt v hlavickovem souboru
 * dostupnem v adresari "/usr/include/linux/fb.h"
 */
typedef struct fb_fix_screeninfo ModeInfo;
 
 
 
/*
 * Precteni vsech relevantnich informaci zjistenych o framebufferu. Pro korektni
 * funkci je zapotrebi, aby mel uzivatel pristup k zarizeni /dev/fb0
 * (postacuje byt ve skupine 'video' ci pouziti su/sudo)
 */
int readFramebufferInfo(int framebufferDevice,
                        FramebufferInfo *framebufferInfoPtr,
                        ModeInfo        *modeInfoPtr)
{
    /* Pokud operace ioctl probehne v poradku, vrati se 0 */
    if (ioctl(framebufferDevice, FBIOGET_VSCREENINFO, framebufferInfoPtr)) {
        perror("Nelze precist informace o framebufferu");
        return 0;
    }
 
    /* Pokud operace ioctl probehne v poradku, vrati se 0 */
    if (ioctl(framebufferDevice, FBIOGET_FSCREENINFO, modeInfoPtr)) {
        perror("Nelze precist informace o rezimu");
        return 0;
    }
    return 1;
}
 
 
 
/*
 * Funkce putpixel platna pro nezname graficke rezimy.
 */
void putpixelNull(const int x, const int y,
                 const char r, const char g, const char b,
                 char *pixels, const int line_length)
{
}
 
 
 
/*
 * Funkce putpixel platna pouze pro graficke rezimy true-color
 * s formatem 8-8-8-8 (popr. muze byt alfa kanal ignorovan).
 * Funkcni napriklad pro graficke karty Intel.
 */
void putpixelBGRA(const int x, const int y,
                 const char r, const char g, const char b,
                 char *pixels, const int line_length)
{
    /* vypocet adresy zapisu dat */
    unsigned int index = (x<<2) + y*line_length;
    /* << 2 nahrazuje nasobeni ctyrmi */
 
    /* vlastni provedeni zapisu */
    *(pixels+index) = b;
    index++;
    *(pixels+index) = g;
    index++;
    *(pixels+index) = r;
}
 
 
 
/*
 * Funkce putpixel platna pouze pro graficke rezimy true-color
 * s formatem 8-8-8-8 (popr. muze byt alfa kanal ignorovan).
 * Funkcni pro Raspberry Pi s poradim bajtu R,G,B,A.
 */
void putpixelRGBA(const int x, const int y,
                 const char r, const char g, const char b,
                 char *pixels, const int line_length)
{
    putpixelBGRA(x, y, b, g, r, pixels, line_length);
}
 
 
 
/*
 * Plati pro format 565
 */
#define RED_OFFSET     11
#define GREEN_OFFSET    5
#define BLUE_OFFSET     0
#define RED_LOST_BITS   3
#define GREEN_LOST_BITS 2
#define BLUE_LOST_BITS  3
#define RED_MASK        0x1f /* 0001 1111 */
#define GREEN_MASK      0x3f /* 0011 1111 */
#define BLUE_MASK       0x1f /* 0001 1111 */
 
 
 
/*
 * Funkce putpixel platna pouze pro graficke rezimy hi-color
 * s formatem 5-6-5.
 */
void putpixel565(const int x, const int y,
                 const char r, const char g, const char b,
                 char *pixels, const int line_length)
{
    /* vypocet barvy pixelu, v zavorce nejdrive snizime bitovou sirku
     * rezervovanou pro jednotlive barvove slozky a posleze bity, ktere
     * reprezentuji barvovou slozku posuneme do spravne pozice ve slove */
    unsigned int pixel_value = (r >> RED_LOST_BITS)   << RED_OFFSET |
                               (g >> GREEN_LOST_BITS) << GREEN_OFFSET |
                               (b >> BLUE_LOST_BITS)  << BLUE_OFFSET;
 
    /* prevod na dvojici bajtu */
    unsigned char byte1 = pixel_value & 0xff;
    unsigned char byte2 = pixel_value >> 8;
 
    /* vypocet adresy zapisu dat */
    unsigned int index = (x<<1) + y*line_length;
    /* << 1 nahrazuje nasobeni dvema */
 
    /* vlastni provedeni zapisu */
    *(pixels+index) = byte1;
    index++;
    *(pixels+index) = byte2;
}
 
 
 
/*
 * Novy datovy typ - ukazatel na (libovolnou) funkci putpixel.
 */
typedef void (*PutpixelFunction)(const int, const int,
                                 const char, const char, const char,
                                 char*, const int);
 
 
 
/*
 * Funkce, ktera vraci korektni funkci pro operaci putpixel().
 */
PutpixelFunction getProperPutpixelFunction(int bits_per_pixel, int type, int visual, int redOffset)
{
    /* umime rozeznat pouze format bez bitovych rovin a bez palety */
    if (type == FB_TYPE_PACKED_PIXELS && visual == FB_VISUAL_TRUECOLOR) {
        /* framebuffer s bitovou hloubkou 16bpp */
        if (bits_per_pixel == 16) {
            return putpixel565;
        }
        /* framebuffery s bitovou hloubkou 32bpp */
        if (bits_per_pixel == 32) {
            /* rozlisujeme podle pozice cervene slozky */
            if (redOffset == 16) {
                return putpixelBGRA;
            }
            else {
                return putpixelRGBA;
            }
        }
    }
    return putpixelNull;
}
 
 
 
/*
 * Nekolik maker pouzitych v algoritmech pro vykreslovani usecek.
 */
#define ABS(x) ((x)<0 ? -(x) : (x))
#define MAX(a,b) ((a)>(b) ? (a) : (b))
#define MIN(a,b) ((a)<(b) ? (a) : (b))
 
 
 
/* Definice pro ANSI C */
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
 
 
 
/*
 * Specialni pripad: vodorovna usecka.
 */
void horizontalLine(const int x1, const int x2, const int y,
                    char *pixels, const int line_length, PutpixelFunction putpixel)
{
    int x;
    int from_x = MIN(x1, x2);
    int to_x = MAX(x1, x2);
    for (x = from_x; x <= to_x; x++) {
        putpixel(x, y, 0xff, 0xff, 0xff, pixels, line_length);
    }
}
 
 
 
/*
 * Specialni pripad: svisla usecka.
 */
void verticalLine(const int x, const int y1, const int y2,
                  char *pixels, const int line_length, PutpixelFunction putpixel)
{
    int y;
    int from_y = MIN(y1, y2);
    int to_y = MAX(y1, y2);
    for (y = from_y; y <= to_y; y++) {
        putpixel(x, y, 0xff, 0xff, 0xff, pixels, line_length);
    }
}
 
 
 
/*
 * Funkce pro vykresleni usecky s aplikaci antialiasingu.
 */
void lineAA(const int x1, const int y1, const int x2, const int y2,
          char *pixels, const int line_length, PutpixelFunction putpixel)
{
    /* specialni pripad - svisla usecka */
    if (x1==x2) {
        verticalLine(x1, y1, y2, pixels, line_length, putpixel);
        return;
    }
 
    /* specialni pripad - vodorovna usecka */
    if (y1==y2) {
        horizontalLine(x1, x2, y1, pixels, line_length, putpixel);
        return;
    }
 
    /* mame smulu a musime pouzit plnou verzi algoritmu */
 
    /* konstanty pouzite pri vykreslovani */
    int dx = x2 - x1;
    int dy = y2 - y1;
    double s, p, e=255.0;
    int x, y, xdelta, ydelta, xpdelta, ypdelta, xp, yp;
    int i, imin, imax;
 
    /* pomocne promenne - pocatecni a koncove body */
    int xx1 = x1 < x2 ? x1 : x2;
    int xx2 = x1 < x2 ? x2 : x1;
    int yy1 = x1 < x2 ? y1 : y2;
    int yy2 = x1 < x2 ? y2 : y1;
 
    /* nastaveni pro sklony mensi nez 45 stupnu */
    if (ABS(dx) > ABS(dy)) {
        s=(double)dy/(double)dx;
        imin=xx1;  imax=xx2;
        x=xx1;     y=yy1;
        xdelta=1;  ydelta=0;
        xpdelta=0;
        xp=0;
        if (yy2>yy1) {
            ypdelta=1;
            yp=1;
        }
        else {
            s=-s;
            ypdelta=-1;
            yp=-1;
        }
    }
    /* nastaveni pro sklony vetsi nez 45 stupnu */
    else {
        s=(double)dx/(double)dy;
        xdelta=0; ydelta=1;
        ypdelta=0;
        yp=0;
        if (yy2>yy1) {
            imin=yy1;    imax=yy2;
            x=xx1;       y=yy1;
            xpdelta=1;
            xp=1;
        }
        else {
            s=-s;
            imin=yy2;    imax=yy1;
            x=xx2;       y=yy2;
            xpdelta=-1;
            xp=-1;
        }
    }
    /* vlastni vykreslovaci smycka (zde bez optimalizaci!) */
    p=s*256.0;
    for (i=imin; i<=imax; i++) {
        int c1 = (int)e;
        int c2 = 255-c1;
        putpixel(x+xp, y+yp, c2, c2, c2, pixels, line_length);
        putpixel(x, y, c1, c1, c1, pixels, line_length);
        e=e-p;
        x+=xdelta;
        y+=ydelta;
        if (e<0.0) {
            e+=256.0;
            x+=xpdelta;
            y+=ypdelta;
        }
    }
}
 
 
 
/*
 * Zvlnena usecka (rekurzivne volana funkce)
 */
void npr_line(int x1, int y1, int x2, int y2, double maxd, char *pixels, const int line_length, PutpixelFunction putpixel)
{
    int dist = ABS(x2-x1) + ABS(y2-y1);
    /* kratka vzdalenost se jiz nakresli jako normalni usecka */
    if (dist < 30) {
        lineAA(x1, y1, x2, y2, pixels, line_length, putpixel);
    }
    else
    {
        int midx, midy;
        double nx, ny;
        double d;
        double nd;
 
        /* vypocet presneho stredu mezi dvema body */
        midx = (x1 + x2) >> 1;
        midy = (y1 + y2) >> 1;
 
        /* prvky normaloveho vektoru */
        nx = (y1 - y2);
        ny = (x2 - x1);
 
        /* normalizace */
        nd = sqrt(nx*nx + ny*ny);
        nx /= nd;
        ny /= nd;
 
        /* posun prostredniho bodu po normale */
        d = maxd * (((float)rand()/RAND_MAX) - 0.5);
        midx += nx * d;
        midy += ny * d;
 
        /* rekurzivni vykresleni prvni a druhe casti */
        npr_line(x1, y1, midx, midy, maxd/1.8, pixels, line_length, putpixel);
        npr_line(midx, midy, x2, y2, maxd/1.8, pixels, line_length, putpixel);
    }
}
 
 
 
void draw_square(char *pixels, const int line_length, PutpixelFunction putpixel)
{
    npr_line(20, 20, 200, 20, 10, pixels, line_length, putpixel);
    npr_line(200, 20, 200, 200, 10, pixels, line_length, putpixel);
    npr_line(20, 200, 200, 200, 10, pixels, line_length, putpixel);
    npr_line(20, 200, 20, 20, 10, pixels, line_length, putpixel);
}
 
 
 
void draw_diamond(char *pixels, const int line_length, PutpixelFunction putpixel)
{
#define W 80
    npr_line(100 - W, 360,     100,     360 - W, 10, pixels, line_length, putpixel);
    npr_line(100,     360 - W, 100 + W, 360,     10, pixels, line_length, putpixel);
    npr_line(100 + W, 360,     100,     360 + W, 10, pixels, line_length, putpixel);
    npr_line(100,     360 + W, 100 - W, 360,     10, pixels, line_length, putpixel);
#undef W
}
 
 
 
void draw_star(char *pixels, const int line_length, PutpixelFunction putpixel)
{
#define STEP 5
    int i;
    float x1, y1, x2, y2;
    for (i=0; i<360; i+=360/STEP)
    {
        x1 = 200.0*cos(i*M_PI/180.0);
        y1 = 200.0*sin(i*M_PI/180.0);
        x2 = 200.0*cos((i+3*360/STEP)*M_PI/180.0);
        y2 = 200.0*sin((i+3*360/STEP)*M_PI/180.0);
        npr_line(400+x1, 320+y1, 400+x2, 320+y2, 10, pixels, line_length, putpixel);
    }
#undef STEP
}
 
 
 
/*
 * Ulozeni obsahu framebufferu do souboru s rastrovym obrazkem.
 */
void saveFramebuffer(const char *filename, FramebufferInfo *framebufferInfoPtr, char *pixels)
{
    const int bpp = framebufferInfoPtr->bits_per_pixel;
    const int xres = framebufferInfoPtr->xres;
    const int yres = framebufferInfoPtr->yres;
 
    FILE *fout = fopen(filename, "wb");
    int i;
    char *adr=pixels;
 
    if (!fout) {
        perror("Unable to open output file");
        return;
    }
 
    /* hlavicka souboru s rastrovym obrazkem */
    fprintf(fout, "P6\n");
    fprintf(fout, "%d\n%d\n255\n", xres, yres);
 
    switch (bpp) {
        case 16: /* 16bitova barvova hloubka, predpokladejme format 565 */
            for (i=0; i<xres*yres; i++) {
                /* nejprve se prectou dva bajty z framebufferu */
                unsigned char b1 = *(adr+1);
                unsigned char b2 = *(adr);
                /* posleze se prevedou na 16bitove slovo */
                unsigned int  color = (b1 << 8) + b2;
                /* a zase ziskame zpetnym prevodem hodnoty barvovych slozek */
                unsigned char r = ((color >> RED_OFFSET)   & RED_MASK)   << RED_LOST_BITS;
                unsigned char g = ((color >> GREEN_OFFSET) & GREEN_MASK) << GREEN_LOST_BITS;
                unsigned char b = ((color >> BLUE_OFFSET)  & BLUE_MASK)  << BLUE_LOST_BITS;
                /* zapis barvovych slozek */
                putc(r, fout);
                putc(g, fout);
                putc(b, fout);
                adr+=2; /* posun na dalsi pixel ve framebufferu */
            }
            break;
        case 24: /* 24bitova barvova hloubka - lze nahradit jedinym zapisem, pokud nebudete prehazovat barvy! */
            for (i=0; i<xres*yres; i++) {
                fwrite(adr+2, 1, 1, fout);
                fwrite(adr+1, 1, 1, fout);
                fwrite(adr+0, 1, 1, fout);
                adr+=3; /* posun na dalsi pixel ve framebufferu */
            }
            break;
        case 32: /* 32bitova barvova hloubka */
            for (i=0; i<xres*yres; i++) {
                fwrite(adr+2, 1, 1, fout);
                fwrite(adr+1, 1, 1, fout);
                fwrite(adr+0, 1, 1, fout);
                adr+=4; /* posledni bajt se busi preskocit, neuklada se */
            }
            break;
    }
    fclose(fout);
}
 
 
 
/*
 * Vykresleni testovaciho obrazku s vyuzitim funkci line a putpixel.
 */
void drawTestImage(int framebufferDevice,
                   FramebufferInfo *framebufferInfoPtr,
                   ModeInfo        *modeInfoPtr)
{
#define OFFSET 300
    /* casto pouzivane konstanty */
    const int buffer_length = modeInfoPtr->smem_len;
    const int pitch = modeInfoPtr->line_length;
 
    /* ziskame spravnou verzi funkce putpixel */
    PutpixelFunction putpixel = getProperPutpixelFunction(framebufferInfoPtr->bits_per_pixel,
                                                          modeInfoPtr->type,
                                                          modeInfoPtr->visual,
                                                          framebufferInfoPtr->red.offset);
 
    /* ziskat primy pristup do framebufferu */
    char *pixels = (char*)mmap(0, buffer_length,
                               PROT_READ | PROT_WRITE,
                               MAP_SHARED, framebufferDevice,
                               0);
 
    if (pixels != MAP_FAILED) {
        int i;
        /* nejprve vymazeme cely framebuffer */
        memset(pixels, 0, buffer_length);
        /* vykreslime nekolik obrazcu */
        draw_square(pixels, pitch, putpixel);
        draw_diamond(pixels, pitch, putpixel);
        draw_star(pixels, pitch, putpixel);
        for (i=0; i<500; i+=40) {
            npr_line(650+i, 0, 650+i, 600, i/6.0, pixels, pitch, putpixel);
        }
        /* ulozeni framebufferu */
        saveFramebuffer("rpi_fb13.ppm", framebufferInfoPtr, pixels);
        /* cekani na stisk klavesy */
        getchar();
        munmap(pixels, buffer_length);
    }
    else {
        perror("Nelze pristupovat k framebufferu");
    }
}
 
 
 
/* Vstupni bod do demonstracniho prikladu... :) */
int main(int argc, char **argv)
{
    FramebufferInfo framebufferInfo;
    ModeInfo        modeInfo;
    int framebufferDevice = 0;
 
    /* Ze zarizeni potrebujeme cist i zapisovat.*/
    framebufferDevice = open("/dev/fb0", O_RDWR);
 
    /* Pokud otevreni probehlo uspesne, nacteme
     * a nasledne vypiseme informaci o framebufferu.*/
    if (framebufferDevice != -1) {
        /* Precteni informaci o framebufferu a test, zda se vse podarilo */
        if (readFramebufferInfo(framebufferDevice, &framebufferInfo, &modeInfo)) {
            drawTestImage(framebufferDevice, &framebufferInfo, &modeInfo);
        }
        close(framebufferDevice);
        return 0;
    }
    /* Otevreni se nezadarilo, vypiseme tudiz pouze chybove hlaseni.*/
    else {
        perror("Nelze otevrit ovladac /dev/fb0");
        return 1;
    }
}
 
 
 
/* finito */

12. Pokračování? GLES

S využitím přímého přístupu k barvám pixelů uložených ve framebufferu je možné realizovat prakticky všechny „klasické“ grafické efekty, které jsou známé ze starších dem, konkrétně z dem vzniklých ještě před nástupem grafických akcelerátorů (nepatrně složitější je implementace efektů s barvovou paletou, i to lze však realizovat). My se však příště začneme zabývat odlišnou oblastí, která je však v kontextu využití jednodeskového mikropočítače Raspberry Pi neméně důležitá (pravděpodobně i důležitější). Jedná se o volání funkcí knihovny GLES (resp. přesněji řečeno OpenGL ES) pro programování čipu Broadcom VideoCore IV. Teprve s využitím GLES (což je nejrozšířenější knihovna pro 3D grafiku) je možné z výpočetních možností nabízených Raspberry Pi skutečně vytěžit maximum, protože VideoCore nemusí být použit pouze pro renderování grafiky, ale i pro běžné výpočty, které mohou probíhat mnohem rychleji, než na hlavním CPU.

Obrázek 7: Screenshot vygenerovaný příkladem rpi_fb13.c (obrázek byl znegován).

cyber23

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

Všechny demonstrační příklady, s nimiž jsme se v dnešním článku seznámili, byly, podobně jako tomu bylo i v obou předchozích částech tohoto seriálu, uloženy do Git repositáře umístěného na GitHubu na adrese (https://github.com/tisnik/pre­sentations):

Pro překlad obou demonstračních příkladů je zapotřebí mít nainstalován překladač GNU C (či Clang), linker a vývojářskou verzi libc. Navíc poslední příklad vyžaduje i slinkování s knihovnou libm, tj. použití přepínače -lm

14. Odkazy na Internetu

  1. Seriál Grafické karty a grafické akcelerátory
    http://www.root.cz/serialy/graficke-karty-a-graficke-akceleratory/
  2. Grafika na osmibitových počítačích firmy Sinclair II
    http://www.root.cz/clanky/grafika-na-osmibitovych-pocitacich-firmy-sinclair-ii/
  3. Xiaolin_Wu's Line Algorithm
    https://en.wikipedia.org/wi­ki/Xiaolin_Wu's_line_algo­rithm
  4. Grafické čipy v osmibitových počítačích Atari
    http://www.root.cz/clanky/graficke-cipy-v-osmibitovych-pocitacich-atari/
  5. Osmibitové počítače Commodore a čip VIC-II
    http://www.root.cz/clanky/osmibitove-pocitace-commodore-a-cip-vic-ii/
  6. Grafika na osmibitových počítačích firmy Apple
    http://www.root.cz/clanky/grafika-na-osmibitovych-pocitacich-firmy-apple/
  7. Počátky grafiky na PC: grafické karty CGA a Hercules
    http://www.root.cz/clanky/pocatky-grafiky-na-pc-graficke-karty-cga-a-hercules/
  8. Karta EGA: první použitelná barevná grafika na PC
    http://www.root.cz/clanky/karta-ega-prvni-pouzitelna-barevna-grafika-na-pc/
  9. Grafické karty MCGA a VGA
    http://www.root.cz/clanky/graficke-karty-mcga-a-vga/
  10. Grafický subsystém počítačů Amiga
    http://www.root.cz/clanky/graficky-subsystem-pocitacu-amiga/
  11. Grafický subsystém počítačů Amiga II
    http://www.root.cz/clanky/graficky-subsystem-pocitacu-amiga-ii/
  12. Raspberry Pi pages
    https://www.raspberrypi.org/
  13. BCM2835 registers
    http://elinux.org/BCM2835_registers
  14. VideoCore (archiv stránek společnosti Alphamosaic)
    http://web.archive.org/web/20030209213838/www­.alphamosaic.com/videocore/
  15. VideoCore (Wikipedia)
    https://en.wikipedia.org/wi­ki/Videocore
  16. RPi lessons: Lesson 6 Screen01
    http://www.cl.cam.ac.uk/pro­jects/raspberrypi/tutorial­s/os/screen01.html
  17. Raspberry Pi forum: Bare metal
    https://www.raspberrypi.or­g/forums/viewforum.php?f=72
  18. C library for Broadcom BCM 2835 as used in Raspberry Pi
    http://www.airspayce.com/mi­kem/bcm2835/
  19. Raspberry Pi Hardware Components
    http://elinux.org/RPi_Har­dware#Components
  20. (Linux) Framebuffer
    http://wiki.linuxquestion­s.org/wiki/Framebuffer
  21. (Linux) Framebuffer HOWTO
    http://tldp.org/HOWTO/Framebuffer-HOWTO/
  22. Linux framebuffer (Wikipedia)
    https://en.wikipedia.org/wi­ki/Linux_framebuffer
  23. RPi Framebuffer
    http://elinux.org/RPi_Framebuffer
  24. 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/
  25. Zdrojový kód fb.c pro RPI
    https://github.com/jncronin/rpi-boot/blob/master/fb.c
  26. RPiconfig
    http://elinux.org/RPi_config.txt
  27. Mailbox framebuffer interface
    https://github.com/raspbe­rrypi/firmware/wiki/Mailbox-framebuffer-interface
  28. Seriál Grafické formáty
    http://www.root.cz/serialy/graficke-formaty/

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