\documentclass[a4paper,twocolumn]{article} \usepackage[german]{babel} \usepackage[T1]{fontenc} \usepackage[latin1]{inputenc} \usepackage[showlabels,sections,floats,textmath,displaymath]{preview} \newbox\chaos \newdimen\tdim \def\fframe{% \tdim=\columnwidth \advance\tdim by -2\fboxsep \advance\tdim by -2\fboxrule \setbox\chaos=\hbox\bgroup\begin{minipage}{\tdim}} \def\endfframe{\end{minipage}\egroup\fbox{\box\chaos}} \unitlength 1mm \newcount\fives \fives 14 \newcount\ones \ones\fives \multiply \ones by 5 \newsavebox{\raster} \savebox{\raster}(\ones,\ones) {\thicklines \put(0,0){\line(0,1){\ones}} \put(0,0){\line(1,0){\ones}} \multiput(0,0)(5,0){\fives} {\begin{picture}(0,0) \put(5,0){\line(0,1){\ones}} \thinlines\multiput(1,0)(1,0){4}{\line(0,1){\ones}} \end{picture}} \multiput(0,0)(0,5){\fives} {\begin{picture}(0,0) \put(0,5){\line(1,0){\ones}} \thinlines\multiput(0,1)(0,1){4}{\line(1,0){\ones}} \end{picture}} } \begin{document} \title{Einfache Kurven auf Rastergrafiken} \author{David Kastrup} \maketitle \begin{abstract} Es sollen hier einfache Methoden vorgestellt werden, um auf einer Rastereinheit verschiedene Kurven darzustellen. Vorgestellt werden Zeichenalgorithmen für Linien, Kreise und Hyperbeln. Die hier hergeleiteten Gleichungen sind auch unter dem Namen {\tt DDA}s bekannt. \end{abstract} \section{Einführung} Bei den hier vorgestellten Algorithmen werden zunächst nur Kurvenstücke betrachtet, die die folgenden Eigenschaften besitzen: \begin{enumerate} \item Sie lassen sich als Funktion $y = f(x)$ darstellen. \item $y$ ist im betrachteten Bereich monoton, das heißt, entweder durchgehend steigend oder durchgehend fallend. \item Wenn $x$ sich um $1$ ändert, so ändert sich $y$ betragsmäßig höchstens um $1$ ($\left|\frac{\partial y}{\partial x}\right| \leq 1$). \end{enumerate} \section{Die gerade Linie} Wir betrachten hier zunächst nur die gerade Linie im ersten Oktanten, die durch den Punkt $0 \choose 0$ geht. Alle anderen Linien lassen sich durch Vertauschen von $x$ und~$y$ sowie Vorzeichenwechsel erzeugen. Im ersten Oktanten gilt $x \geq y \geq 0$. Zum Zeichnen einer Linie genügt es also, $x$ durchlaufen zu lassen und für $y$ die dazugehörigen Werte zu berechnen und zu runden. Die Gleichung einer Geraden durch $\Delta x \choose \Delta y$ lautet: \begin{equation} \label{lgi} y = \frac{\Delta y}{\Delta x}x \end{equation} % Nun stellen wir $y$ als Summe eines ganzzahligen Wertes $e$ und eines gebrochenen Wertes $f$ dar, für den gilt: $-0.5 \leq f < 0.5$. Somit stellt dann $e$ den gewünschten, auf die nächste ganze Zahl gerundeten $y$-Wert dar. Jetzt formen wir (\ref{lgi}) um: \begin{eqnarray} e + f &=& x \frac{\Delta y}{\Delta x}\nonumber\\ e \Delta x + f \Delta x &=& x \Delta y\nonumber\\ f \Delta x - \left\lceil\frac{\Delta x}2\right\rceil &=& x \Delta y - e \Delta x - \left\lceil\frac{\Delta x}2\right\rceil \label{lgii} \end{eqnarray} % Den linken Ausdruck in (\ref{lgii}) bezeichnen wir jetzt mit $d$. Für positive gerade Werte von $\Delta x$ ist offensichtlich $d < 0$ eine zu~$f < 0.5$ equivalente Bedingung. Für ungerade Werte von~$\Delta x$ ist $f < 0.5$ equivalent zu $d + 0.5 < 0$. Da $d$ stets eine ganze Zahl ist, ist dies wieder zu $d < 0$ equivalent. % INTENTIONAL ERRORS! INTENTIONAL ERRORS! INTENTIONAL ERRORS! % % The following line should flag a PostScript error when previewing, % but processing of other previews should continue. % Wird nun $\ifPreview\special{ps: junk}\fi f \geq 0.5$, wie sich durch den Vergleich $d \stackrel{?}{<} 0$ feststellen läßt, so muß man korrigieren, indem man $f$ um~1 erniedrigt und $e$ um~$1$ erhöht. % % The following line will make Ghostscript abort unexpectedly when % previewing, but processing of other previews should continue. % $\ifPreview\special{ps: quit}\fi d$ muß dann auch entsprechend angepaßt werden. Mit den angegebenen Formeln ergibt sich jetzt bei Berücksichtigung der Einflüsse von $x$ und $e$ auf $d$ der in Tabelle~\ref{linalg} angegebene Algorithmus. Eine optimierte C-function, die die Oktantenaufteilung berücksichtigt, ist in Tabelle~\ref{linc} zu finden. Einige hiermit gezeichnete Linien sind in Abbildung~\ref{linpict} zu sehen. \begin{table} \caption{Linienzugalgorithmus} \label{linalg} \begin{fframe} \begin{enumerate} \item Setze $x \gets 0$, $y \gets 0$, $d \gets -\left\lceil\frac{\Delta x}2\right\rceil$ \item Wiederhole bis $x = \Delta x$ \begin{enumerate} \item Zeichne Punkt an $x \choose y$ \item Setze $x \gets x + 1$, $d \gets d + \Delta y$ \item Falls $d \geq 0$ \begin{enumerate} \item Setze $d \gets d - \Delta x$ \item Setze $y \gets y + 1$ \end{enumerate} \end{enumerate} \end{enumerate} \end{fframe} \end{table} \begin{table} \caption{Linienziehen in C} \label{linc} \begin{fframe} \small \begin{verbatim} extern int x,y; /* (x,y) ist Koordinate des nicht * gezeichneten Startpunktes, zeigt * nachher auf gezeichneten Endpunkt */ #define doline(dx,dy,advx,advy) { \ d = -(((i = dx) + 1) >> 1); \ while (i--) { \ advx; \ if ((d += dy) >= 0) { \ d -= dx; advy; \ } \ dot(x,y); \ } \ return; \ } /* Grundalgorithmus 1. Oktant */ /* dx ist Distanz in unabh. Richtung, * * dy in abh. Richtung, advx geht * * in unabh. Richtung, advy in abh. */ #define docond(cond,advx,advy) { \ if (cond) doline(dx,dy,advx,advy) \ doline(dy,dx,advy,advx) \ } /* Grundalgorithmus 1./2. Oktant */ /* cond ist true falls |dx| > |dy| */ void linedraw(int dx, int dy) /* Von (x,y) nach (x+dx, y+dx). */ { int i; if (dx >= 0) { if (dy >= 0) docond(dx > dy, ++x, ++y) docond(dx > (dy = -dy), ++x, --y) } if (dy >= 0) docond((dx = -dx) > dy,--x,++y) docond((dx = -dx) > (dy = -dy), --x, --y ) } \end{verbatim} \end{fframe} \end{table} \begin{figure} \begin{picture}(\ones,\ones) \put(0,0){\usebox{\raster}} \newcount\x \newcount\y \newcount\d \newcount\dx \newcount\dy \x 0 \y 0 \dx \ones \dy \ones \loop %{ \d -\dx \divide \d by 2 %} \ifnum \dy > 0 %{ {\loop %{ \put(\x,\y){\circle*{1}}%} \ifnum \x < \ones %{ \advance \x by 1 \advance \d by \dy %} \ifnum \d > -1 %{ \advance \y by 1 \advance \d by -\dx %} \fi %}} \repeat} \advance \x by 5 \advance \dx by -5 \advance \dy by -15 %} \repeat \end{picture} \caption{Einige Linien}\label{linpict} \end{figure} \section{Der Kreis} Wir betrachten hier nur den Achtelkreis im zweiten Oktanten ($y \geq x \geq 0$). Hier gelten die oben angegebenen Beziehungen. Alle anderen Achtelkreise lassen sich durch elementare Spiegelungen erzeugen. Die Gleichung eines Kreises ist hier \begin{equation} y = ±\sqrt{r^2 - x^2} \end{equation} Der Wert $y$ läßt sich darstellen als Summe einer ganzen Zahl $e$ und einem Wert $f$ mit $-0.5 \leq f < 0.5$. Der Wertebereich von $f$ ist so gewählt worden, damit $e$ einen auf ganze Zahlen gerundeten Wert für $y$ darstellt. Nun gilt: \begin{eqnarray} e + f&=&\sqrt{r^2 - x^2}\nonumber\\ \label{ggg}e^2 + 2ef + f^2&=&r^2 - x^2 \end{eqnarray} % Die Gleichung (\ref{ggg}) hat für $x+1$ folgende Form: \begin{eqnarray} \label{hhh}e'^2 + 2e'f' + f'^2&=&r^2 - x^2 - 2x -1 \end{eqnarray} % Zieht man die Gleichung (\ref{ggg}) von (\ref{hhh}) ab, so ergibt sich nach Umsortieren: \begin{eqnarray*} e' = e:\\ 2e'f' + f'^2&=&2ef+f^2-2x-1\\ e' = e-1:\\ 2e'f' + f'^2&=&2ef+f^2+2e-2x-2 \end{eqnarray*} % Jetzt wird $2ef + f^2$ mit $m$ getauft. Also: \begin{eqnarray*} e' = e:\\ m'&=&m -2x-1\\ e' = e-1:\\ m'&=&m +2e-1 -2x-1 \end{eqnarray*} Wie groß ist jetzt $m$? Für $x=0$ ist es sicher $0$. Solange $e$ konstant bleibt, schrumpft $f$ stetig. Fällt $f$ unter $-0.5$, so fällt $m$ (unter Vernachlässigung von $f^2$) unter $-e$. Dies wird jetzt als Kriterium für einen Unterlauf von $f$ verwendet. Tritt dieser auf, so muß $f$ um $1$ erhöht und $e$ um $1$ erniedrigt werden. Um die Abfragebedingung zu vereinfachen, setzt man jetzt $q$ = $m+e$. Der resultierende Algorithmus ist in Tabelle \ref{alg}, ein optimiertes C-Programm ist in Tabelle \ref{prog} zu finden. \begin{table} \caption{Kreiszeichenalgorithmus}\label{alg} \begin{fframe} \begin{enumerate} \item Setze $x\gets 0$, $y\gets r$, $q\gets r$ \item Wiederhole bis $x>y$: \begin{enumerate} \item Setze einen Punkt an $x \choose y$. \item Setze $q\gets q-2x-1$ \item Falls $q<0$ \begin{enumerate} \item Setze $q\gets q + 2y-2$ \item Setze $y\gets y-1$ \end{enumerate} \item Setze $x\gets x+1$ \end{enumerate} \end{enumerate} \end{fframe} \end{table} \begin{table} \caption{Kreiszeichenprogramm}\label{prog} \begin{fframe} \small \begin{verbatim} void fourfold(int x0, int y0, int x, int y) /* Zeichne in Oktant 1,3,5,7 */ /* Wird benutzt, um Anfangs- und End- * * Punkte nicht zweimal zu zeichnen */ { dot(x0+x,y0+y); dot(x0-y,y0+x); dot(x0-x,y0-y); dot(x0+y,y0-x); } void eightfold(int x0, int y0, int x, int y) /* Zeichne in allen Quadranten */ { fourfold(x0,y0,x,y); /* 1357 */ fourfold(x0,y0,x,-y); /* 8642 */ } void circle(int x0, int y0, int r) { fourfold(x0,y0,0,r); /* Die ersten vier Punkte */ for (x=0, y=q=r;; ) { if ((q -= 2* x++ + 1) < 0) q += 2* --y; if (x >= y) break; eightfold(x0,y0,x,y); } if (x==y) fourfold(x0,y0,x,y); /* Eventuell die letzten vier */ } \end{verbatim} \end{fframe} \end{table} \begin{figure} \begin{picture}(\ones,\ones) \put(0,0){\usebox{\raster}} \newcount\x \newcount\y \newcount\q \loop {\x 0 \y \ones \q \ones \loop \put(\x,\y){\circle*{1}} \put(\y,\x){\circle*{1}} \advance \q by -\x \advance \x by 1 \advance \q by -\x \ifnum \x < \y \ifnum \q < 0 \advance \y by -1 \advance \q by \y \advance \q by \y \fi \repeat} \advance \ones by -10 \ifnum \ones > 0 \repeat \end{picture} \caption{Viertelkreise}\label{zeich} \end{figure} \section{Einfache Hyperbeln} Als letztes Beispiel betrachten wir hier Hyperbeln, die der Formel $y = r^2\!/x$ genügen, und zwar im Bereich~$x \geq r$. Mit dem Ansatz $y = e + f$ ergibt sich: \begin{eqnarray} e+f &=& r^2\!/x\nonumber\\ ex + fx &=& r^2\nonumber\\ fx &=& r^2 - ex\label{phyp} \end{eqnarray} \pagebreak[2] Für $x' = x+1$ hat nun (\ref{phyp}) die Form \begin{eqnarray*} e' = e:\\ f'x' &=& r^2 - ex - e\\ e' = e - 1:\\ f'x' &=& r^2 - ex - e + x + 1 \end{eqnarray*} Setzt man jetzt $d = (2f + 1)x$, so ist $f < -0.5$ mit~$d < 0$ equivalent. Erreicht man diesen Fall unter Verwendung der Annahme $e' = e$, dann muß in bekannter Weise $f$ um~$1$ erhöht und $e$ um~$1$ vermindert werden. \pagebreak Für $d'$ ergeben sich dann die folgenden Werte: \begin{eqnarray*} e' = e:\\ d' &=& d - 2e + 1\\ e' = e - 1:\\ d' &=& d - 2e + 2x + 2 + 1 \end{eqnarray*} Daraus ergibt sich der in Tabelle~\ref{halg} angegebene Hyperbelalgorithmus für den ersten Oktanten. \begin{table} \caption{Hyperbelalgorithmus}\label{halg} \begin{fframe} \begin{enumerate} \item Setze $d \gets r$, $x \gets r$, $y \gets r$ \item Wiederhole bis zufrieden \begin{enumerate} \item Setze Punkt an $x \choose y$ \item Setze $x \gets x + 1$ \item Setze $d \gets d - 2y + 1$ \item Falls $d < 0$ \begin{enumerate} \item Setze $d \gets d + 2x$ \item Setze $y \gets y - 1$ \end{enumerate} \end{enumerate} \end{enumerate} \end{fframe} \end{table} \begin{table} \caption{Hyperbeln in C} \begin{fframe} \small \begin{verbatim} void four(int x0, int y0, int x, int y) /* Hyperbeln sind nur in 4 Oktanten */ { dot(x0+x,y0+y); dot(x0+y,y0+x); dot(x0-x,y0-y); dot(x0-y,y0-x); } void hyperb(int x0, int y0, int r, int dx) { int d, x, y; for (x = y = d = r; dx--;) { four(x0,y0,x,y); ++x; if ((d -= 2*y + 1) < 0) { d += 2*x; --y; } } } \end{verbatim} \end{fframe} \end{table} \begin{figure} \begin{picture}(\ones,\ones) \put(0,0){\usebox{\raster}} \newcount\x \newcount\y \newcount\q \newcount\r \r\ones \loop \advance \r by -10 \ifnum \r > 0 {\x \r \y \r \q \r \loop \put(\x,\y){\circle*{1}} \put(\y,\x){\circle*{1}} \ifnum \x < \ones \advance \x by 1 \advance \q by -\y \advance \q by -\y \advance \q by 1 \ifnum \q < 0 \advance \q by \x \advance \q by \x \advance \y by -1 \fi \repeat} \repeat \end{picture} \caption{Hyperbeln}\label{hzeich} \end{figure} \end{document}