Šedočerná grafika Palmu (2)

Pokračování článku z naší programátorské rubriky.

V minulém dílu jsme si ukázali, jak nastavit oblast v paměti Palm Pilota jako paměť displeje. Cokoliv, co jsme do této oblasti v paměti zapsali, se objevilo na displeji, i když jen černobíle. Ukázali jsme si obrázek, který popisoval, jak jsou jednotlivé bity této oblasti paměti přiřazeny jednotlivým obrazovým bodům displeje (pixelům).

Jak nakreslit bod

V praxi však častěji potřebujeme funkci, která nakreslí jeden bod (černý nebo bílý) na zadaných souřadnicích. Pro popis souřadnic použijeme stejnou konvenci, kterou využívají funkce systému PalmOS: bod se souřadnicemi [0, 0] bude v levém horním rohu displeje.

Teď potřebujeme jen převést souřadnice x a y na adresu v paměti displeje, na které je uložen byte, ve kterém je potřeba nastavit nebo vymazat příslušný bit. Jeden řádek displeje má 160 obrazových bodů (hodnotu je možné přečíst z registru ovladače displeje LXMAX). Těchto 160 obrazových bodů odpovídá oblasti paměti v délce 20 byte.

První byte každého řádku displeje bude tedy mít adresu 20 * y nebo obecně POCET_BYTE_NA_RADEK * y. Souřadnici x musíme použít nejen k výpočtu, který byte na řádku budeme měnit, ale i ke zjištění, který bit musíme nastavit nebo vymazat. Při černobílém zobrazení odpovídá jeden byte 8 obrazovým bodům (23).

Budeme-li dělit hodnotu souřadnice x osmi, tak výsledek dělení udává, o kolikátý byte na řádku se jedná. Zbytek po dělení určuje, který bit bude měněn. Namísto dělení osmi můžeme - pro urychlení - použít posun doprava o 3 bity (ale nemusíme, protože inteligentní překladač to udělá za nás). Podobně můžeme místo zbytku po dělení osmi ponechat pouze spodní tři bity (pomocí funkce logického součinu, AND).

Vzpomeňte si na obrázek v minulém článku. První obrazový bod zleva odpovídá nejvyššímu bitu v příslušném byte. Jednou z metod, jak s příslušným bitem (odpovídajícím obrazovému bodu) pracovat, je převést souřadnici x na masku, ve které má logickou hodnotu 1 jen jediný bit. Poté můžeme pomocí funkce logického součtu (OR) původní hodnoty a této masky nastavit příslušný obrazový bod. Vymazání obrazového bodu je jen o málo složitější: musíme provést operaci logického součinu s jednotkovym doplňkem masky. Příklad funkce pro nastavení barvy bodu je uveden v následujícím rámečku:

#define POCET_BYTE_NA_RADEK 20

void CernobilyBod(UInt8* adresa, UInt16 x, UInt16 y, Boolean nastav)
{
	UInt8* pixels = adresa + (POCET_BYTE_NA_RADEK * y) + (x / 8);
	UInt8 maska = 0x80 >> (x & 0x07);

	if(nastav)
	{
		*pixels |= maska;
	}
	else
	{
		*pixels &= ~maska;
	}
}

Tato funkce je maximálně zjednodušená. Nekontroluje správnost zadaných souřadnic, ani není nijak optimalizována - je zde uvedena hlavně pro názornost. I v této podobě je však rychlejší než funkce operačního systému pro kreslení, zejména když potřebujeme nakreslit jednotlivé body. Příklad programu, který umožňuje kreslit na displeji, je uveden zde. Dotkneme-li se perem displeje, nakreslí se na tomto místě bod. Dotkneme-li se některého tlačítka nebo napíšeme-li nějaký znak, tak bude celý displej vyplněn - po jednom bodu - černou barvou.

Pro srovnání je zde uveden podobný program, který pro vykreslování bodů používá funkci WinDrawLine(), která je v PalmOS do verze 3.5 jediným způsobem, jak nakreslit jeden bod. Od PalmOS verze 3.5 se teprve objevuje funkce WinDrawPixel() pro nakreslení jednoho bodu.

Čáry a Bresenhamův algoritmus

Naučili jsme se kreslit body a to poměrně rychle. Mnohem častěji však budeme potřebovat nakreslit na displeji úsečky - zpočátku černobílé, později i ve stupních šedi. Pro kreslení úseček existuje mnoho algoritmů. Jednoduchým (ale pomalým) způsobem je použití rovnice úsečky ([XP, YP] jsou souřadnice počátku a [XK, YK] jsou souřadnice konce úsečky:

(y - YP) = (YK - YP) * (x - XP) / (XK - XP)

Tento algoritmus vykreslování úsečky podle její rovnice je sice velmi jednoduché naprogramovat, ale to zde uvádět nebudu. Pokud si to přejete vyzkoušet, nezapomeňte odlišit dva případy podle sklonu úsečky (například úsečku rovnoběžnou s osou Y nemůžeme vykreslovat krokováním podle osy X). Existuje totiž mnohem elegantnější řešení, využívající Bresenhamův algoritmus. Tento algoritmus je rychlý, jednoduchý a dává lepší výsledky.

Pro následující úvahy se omezíme na popis úseček, které leží v prvním oktantu, ve kterém platí, že [XP, YP] = [0, 0] a XK >= YK >= 0 (jeho hranice je vymezena na následujícím obrázku fialovou a modrou čarou). Všechny uvažované hodnoty budou celočíselné. Později můžeme pouhým prohozením souřadnic rozšířit tento algoritmus tak, aby pracoval ve všech oktantech. V tomto oktantu platí, že každé celočíselné souřadnici na ose X odpovídá právě jeden bod úsečky. První bod úsečky leží na souřadnicích [0, 0] a pro každý další bod platí, že má souřadnici Y buď stejnou jako bod předchozí, nebo o jedničku vyšší. Situaci ilustruje obrázek:

Pro každý bod úsečky počítáme chybový člen E - vzdálenost mezi ideální přímkou a zobrazovaným bodem, měřenou na ose Y. Je-li chyba menší než polovina pixelu, je výhodnější ponechat stejnou souřadnici Y, jinak je hodnota Y zvětšena o jednu. Při vhodné inicializaci chybového členu stačí testovat jeho znaménko. Fragment Bresenhamova algoritmu je v následujícím rámečku (nezdržuju se s deklaracemi - všechny proměnné jsou celočíselné a se znaménkem):

void Cara(XK, YK)
{
	 y = 0;
	 E = 2 * YK - XK; 
	 
	 for(x = 0; x <= XK; i++)
	 {
	  	  BOD(x, y);
		  
		  if(d < 0)
		  {
		   	   E += 2 * YK;
		  }
		  else 
		  {
		   	   y++;
			   E += 2 * (YK - XK);
		  }		  
	 }
}

Pro praktické použití tohoto algoritmu je potřeba testovat, ve kterém oktantu leží koncový bod úsečky a podle toho upravit, na které ose bude ležet nezávislá proměnná = proměnná cyklu (v obrázku odlišeno světlou a tmavou barvou). Také je třeba upravit, zda bude závislá proměnná inkrementována nebo dekrementována (odlišeno červenými značkami plus a mínus):

Jednoduchý program, využívající kompletní implementaci Bresenhamova algoritmu pro všechny oktanty můžete nalézt zde. Funguje jednoduše - dotknete-li se perem displeje, nakreslí čáru od středu do bodu na zvolené souřadnici. Zadáním znaku nebo dotekem na hardwarovém tlačítku se displej překreslí černou barvou a můžete dál kreslit inverzně. Displej Palm Pilota je na obrázku dole:

Příště dokončíme popis černobílého displeje a technik, které je možné používat pro černobílé kreslení - ukážeme si (já vím, sliboval jsem to minule, ale sem se to nevešlo) co je technika double-buffering, jak počítat v pevné desetinné čárce a využívat virtuální displej a dostane se i na plynulý posuv obrázku.