25.1. Elipses Aparte de las líneas, los círculos y las elipses son de los elementos gráficos más comúnmente utilizados.
25.1.1. Algoritmo del punto medio para dibujar círculos. La misma lógica del algoritmo de Bresenham para dibujar líneas vista en el capítulo 23.1.2. puede ser aplicada al círculo, que puede ser dibujado sin trigonometría y sin multiplicaciones, por eso se le suele conocer como algoritmo de Bresenham para dibujar círculos. La ecuación de una circunferencia que tiene su centro en el punto (x 0, y 0) y de radio R es: (x-x 0)
2
2
+ (y-y 0)
2
= R
Si suponemos que su centro es el origen de coordenadas, entonces la
fórmula queda así: 2
2
2
x + y = R .
Las circunferencias son simétricas respecto de los ejes x, y de las líneas y=x, y=-x. De modo que basta dibujar los puntos de la circunferencia en su primer octante, el resto de puntos se calculan por simetría.
Fig. 25-01 - Octantes
En el primer octante la y cambia más rápidamente que la x, por lo que la
incrementaremos siempre y buscaremos el valor de la x a partir de ella. Comenzaremos en el eje de las ordenadas en el sentido contrario a las
agujas del reloj, decrementando la x cuando toq ue y incrementado siempre la y. Los puntos que dibujemos pueden cumplir o no la ecuación de la circunferencia vista arriba, x 2 + y 2 = R 2. En todo caso buscaremos el punto que diste lo mínimo posible del valor teórico. Para ello definimos la función del error cometido: E i
2
2
2
= x + y - R i i
Fig. 25-02 - Situación del punto respecto a la circunferencia
Partiendo de un punto (x i, yi) se trata de encontrar el valor del siguiente punto (xi+1, yi+1). Ya hemos dicho que siempre y i+1 = y i + 1 en nuestro primer octante. Tenemos que encontrar x i+1. Sólo hay dos posibilidades: y
xi+1 = xi
y
xi+1 = xi-1
Cogeremos uno u otro en función del menor error que origine, da igual que esté dentro o fuera de la circunferencia. Por tanto, para calcular qué error es menor tendremos que elevarlo al cuadrado y compararlos. 1. (1) Supongamos que x i+1 = xi-1, entonces el error es: [(xi-1)2 + (y i+1)2 - R 2]2 = [x i2 - 2x i + 1 + yi2 + 2yi + 1 - R 2]2 = [x i2 + y i2 - R 2 - 2x i + 2y i + 2] 2 = [xi2 + y i2 R2 + 2yi + 1]2 + (1 - 2x i)2 + 2(1 - 2x i)(x i2 + y i2 - R 2 + 2yi + 1). 2. (2) Supongamos que x i+1 = xi entonces el error generado [xi2
es: 2
2 2
+ (y i+1) - R ] =
[x i2
+
yi2
2 2
+ 2y i + 1 - R ] = [x i2
+ yi2 - R 2 + 2yi + 1]2. (1) < (2) <=> (1 - 2x i)2 + 2(1 - 2x i)(xi2 + yi2 - R2 + 2yi + 1) < 0 <=> (1 - 2xi)[(1 - 2x i) + 2(x i2 + yi2 - R2 + 2yi + 1)] < 0 <=> (1 - 2x i) + 2(xi2 + yi2 - R2 + 2yi + 1) > 0 si (1 - 2x i) < 0 <=> 2[(xi2 + yi2 - R2) + (2yi + 1)] + (1 - 2xi)> 0 Sean:
RE =
2
x i
2
2
+ y - R i
XChange =
1 - 2x i
YChange =
2y + 1 i
Los valores iniciales serán X = R, Y = 0 y, por tanto, R E = 0, XChange = 12R, YChange = 1. Cómo cambian estas variables en función de su valor anterior cuando cambien X e Y: RE(i)
x i2
=
YChange(i)
yi2
+
=
2yi
R2
+
1
XChange(i) = 1 - 2xi y
Independientemente
del
valor
de
x i:
YChange(i+1) = 2(y i+1) + 1 = 2y i + 2 + 1 = YChange(i) + 2. Siempre y
Si xi+1 = xi - 1 entonces: y
RE(i+1) = (xi+1)2 + (y i+1)2 - R 2 = x i2 - 2x i + 1 + yi2 + 2y i + 1 - R 2= (xi2 + yi2 - R 2 ) + (2y i + 1) + (1 - 2x i) = RE(i) + YChange(i) + XChange(i)
y
XChange(i+1) = 1 - 2(x i - 1) = 1 - 2x i + 2 = XChange(i) + 2
y
Si xi+1 = xi entonces: y
RE(i+1) = xi2 + (yi + 1)2 - R 2 = xi2 + y i2 + 2yi + 1 - R 2 = (xi2 + yi2 - R 2 ) + (2yi + 1) = RE(i) + YChange(i)
y
XChange(i+1) = 1 - 2x i = XChange(i)
Por lo tanto: RE(i+1) = RE(i) + YChange(i) [+ XChange(i)] YChange(i+1) = YChange(i) + 2 siempre XChange(i+1) = XChange(i) [+ 2]
Donde lo que hay entre corchetes se da sólo en el caso en que x i+1 = xi - 1. El pseudocódigo sería el siguiente: ; Valores X = R
iniciales
Y = 0 XChange = 1 -2R YChange = 1 RE = 0 while x <= y DrawPoint(x,y)
; Para los 8 octantes
Temp = 2(RE +YChange)+XChange Inc(Y) RE += YChange YChange += 2 if
Temp > 0
inc(X)
RE += XChange XChange += 2 end if endwhile
25.1.2.Círculos y razón de aspecto De la misma forma a como hemos visto en el capítulo anterior sobre cómo afecta la razón de aspecto al visionado de un cuadrado, igual ocurre con los círculos. Si dibujáramos un círculo tal cuál en la pantalla, su aspecto sería el de una elipse debido a la razón de aspecto. Por tanto, la única forma de dibujar circunferencias en la pantalla es dibujando elipses aplicándoles la razón de aspecto. Veamos a continuación, de forma esquemática, cuál sería la salida por pantalla en modo 13h de una circunferencia matemática de radio 100 pixels:
Fig. 25-03 - Circunferencia teórica
Importante
Para observar mejor los ejemplos que vienen a continuación, mejor ejecútense, para verlo con mayor claridad. BcirCM1 BcirCF1 BcirCN1 Resultado en pantalla
Código
Código
Código
[bin]
[bin]
[bin] Source 25-01 - Circunferencia Bresenham
El código se ha preparado con recorte, como se puede ver. Además,
comprobaremos en la pantalla que la circunferencia teórica se asemeja más a un huevo que a una circunferencia. Para evitar la razón de aspecto en el dibujo de esta figura echaremos mano de las elipses que, debidamente achatadas nos darán la impresión de circunferencias. Los programas recién comentados han sido modificados para usar el código de las elipses: BcirCM2 BcirCF2 BcirCN2 Resultado en pantalla
Código
Código
Código
[bin]
[bin]
[bin]
Source 25-02 - Circunferencia Bresenham con razón de aspecto
La figura de la izquierda es una circunferencia, la de la derecha arriba es un círculo y la de abajo son circunferencias concéntricas.
25.1.3. Algoritmo para dibujar elipses La elipse tiene simetría par e impar, es decir, podemos restringirnos al cálculo de un cuadrante de una elipse para replicarla en el resto, esto implica que tendremos que calcular los 90º del cuadrante y no sólo 45º como ocurría en la circunferencia, debido al grado de crecimiento de las variables x e y como ocurría con las líneas, habrá que diferenciar cada caso. Recordemos que la circunferencia tiene simetría para cada octante.
25.1.3.1. Conversión de barrido de una elipse Podemos usar la forma algebraica d e una elipse para calcular las coordenadas x e y para todos los puntos que representan una elipse dada. La ecuación de una elipse es la siguiente:
Fig. 25-04 - Ecua ción de la elipse
Esta ecuación describe una elipse centrada en (xc, yc) con los ejes mayor y
menor a y b paralelos a los ejes x e y. Sin embargo, el cálculo necesario para dibujar elipses resolviendo esta ecuación es muy complejo. Las operaciones de multiplicación, división y raíz cuadrada para determinar cada una de las coordenadas consumen mucho tiempo.
25.1.3.2. Algoritmo incremental Al igual que el algoritmo incremental que presentamos anteriormente para dibujar circunferencias, no fue inventado por Bresenham, si bien, la técnica usada es similar, por lo que también se le conoce por el algoritmo de Bresenham. Después de dibujar un pixel, el algoritmo selecciona cuál es el siguiente de sus vecinos que está más cerca de la elipse real. Si suponemos que nuestra elipse está centrada en el origen de coordenadas
y los ejes mayor y menor coinci diendo con los ejes x e y, el algoritmo resultante para dibujarla será mucho más sencillo.
Fig. 25-05 - Elipse de c entro el origen
La ecuación de esta elipse centrada en el origen sería:
Fig. 25-06 - Ecuación de una elipse centrada en el origen
Multiplicando por a 2b2 y pasando a un sólo lado: b2x2 + a2y2 = a2b2. 2 2 (1) b 2 x + a2 y - a2 b2
El valor de esta ecuación para un punto P(x i, yi) nos dice dónde cae
respecto a la elipse.
y
Si el valor es cero, el punto medio está en la
elipse. y
Si el valor es negativo, este punto estará dentro
de la elipse. y
Si el valor es positivo, este punto está fuera de la
elipse. Realmente nos es indiferente la posición del punto respecto de la elipse, tan sólo queremos minimizar la distancia. Por lo tanto definimos la func ión error, que nos da la distancia del punto elegido a la elipse teórica: (2)
E (x i,
2
2
2
2
2
2
y + a y - a b i ) = | b x i i
|
En el primer cuadrante de la elipse, la tangente es siempre negativa. Si
empezamos en el x-eje y avanzamos en el sentido contrario a las agujas del reloj, la pendiente es negativa y el valor absoluto muy grande, lo que significa que la y-coordenada varía mucho más rápido que la x -coordenada. Pero una vez que la pendiente alcanza el valor -1, es a la inversa y la xcoordenada cambia más rápido que la y-coordenada. Por lo tanto tenemos que calcular dos conjuntos de puntos para las dos tipologías que hemos descrito. En el primer conjunto siempre incrementaremos la y -coordenada y buscaremos cuándo decrementar la x-coordenada; mientras que en el segundo siempre decrementaremos la x-coordenada y buscaremos cuándo incrementar la y-coordenada. El valor que escojamos será aquel que minimice la función error (2). Veamos gráficamente lo que queremos decir para ambos conjuntos de puntos:
Fig. 25-07 - Método del Punto Medio -tangente<-1
Fig. 25-08 - Método del Punto Medio - tangente>-1
En la ilustración superior vemos que el punto previo del que partimos,
dibujado en azul, es (x i-1,y i-1), mientras que siempre tenemos dos puntos siguientes sobre los que elegir, A y B, dibujados en naranja. Por ejemplo, para el primer conjunto de puntos, a partir de un punto (x i, y i) decidiremos si xi+1 es xi o x i-1, para aquél que minimice el error provocado. Para ello compararemos las funciones E(x i - 1, yi+1) con E(x i,y i+1). Obsérvese que en este conjunto de puntos y i+1 = yi + 1 siempre. y
(3) E(xi - 1, y i + 1) = |b2(x i - 1)2 + a2(y i + 1)2 b2a2| = |b2xi2 - 2b2xi + b2 + a2yi2 + 2a2y i + a2 b2a2| = |b2xi2 + a2yi2 - b 2a 2 + b2(1-2xi) + a2(1 + 2yi)|
y
y
(4) E(xi, yi + 1) = |b2xi2 + a2(y i + 1)2 - b 2a2| = |b2xi2 + a2y i2 + 2a2yi + a2 - b 2a2| = |b2xi2 + a 2yi2 b2a2 + a2(1 + 2y i)|
y
(3) < (4) <=> (3)2 < (4)2. y
(3)2 = [b2x i2 + a 2yi2 - b 2a2 + a2(1 + 2yi)]2 + [b2(1-2xi)]2 + 2[b2(1-2xi)] [b2xi2 + a2y i2 - b2a 2 + a2(1 + 2yi)]
y
(4)2 = [b2xi2 + a2yi2 - b2a2 + a2(1 + 2yi)]2
(3)2 < (4)2 <=> [b2(1-2xi)] + 2[b 2xi2 + a2yi2 - b2a 2 + a 2(1 + 2y i)] < 0. Suponiendo que (1 - 2xi) > 0. Entonces: y
Que sea menor que cero, significa que el punto x i
- 1 provoca el menor error. y
Si fuera positivo significaría que x i es la elección
correcta.
y
Si fuera cero, podríamos escoger cualquiera de
los dos. Definimos: XChange = b 2 (1-2x i) 2
YChange = a (1 + 2y i) E
2 2 = b2 x + a2 y - b2 a2 i i
Con lo que (3) < (4) <=> XChange + 2(E+YChange) < 0. Además, decimos que partimos en el X-eje y nos movemos en el sentido contrario a las agujas del reloj, por lo que los valores iniciales son: X = a Y = 0 2
XChange = b (1-2a) YChange = a 2 E
= 0
Estas tres variables pueden calcularse recursivamente. y
Si xi+1 = xi - 1 y
XChange = b 2(1 - 2(xi - 1)) = b 2(1 - 2x i + 2) = b2(1 - 2x i) + 2b2
y
YChange = a2(1 + 2(yi + 1)) = a2(1 + 2yi + 2) = a2(1 + 2yi) + 2a2
y
E = b2(xi - 1) 2 + a2(y i + 1)2 - a 2b2 = b2(x i2 -
2xi + 1) + a 2(yi2 + 2yi + 1) -a 2b2 = b2xi2 2b2xi + b2 + a2yi2 + 2a2yi + a2 - a2b2 = b2xi2 + a2yi2 - a2b 2 + b2(1 - 2x i) + a2(1 + 2y i) Entonces:
y
y
XChange(i+1) = XChange(i) + 2b 2
y
YChange(i+1) = YChange(i) + 2a 2
y
E(i+1) = E(i) + XChange(i) + YChange(i)
Si xi+1 = xi y
XChange(i+1) = XChange(i)
y
YChange(i+1) = YChange(i) + 2a 2
y
E = b2x i2 + a2(y i + 1) 2 - a2b 2 = b2xi2 + a2(yi2
+ 2y i + 1) -a 2b2 = b 2xi2 + a 2yi2 + 2a 2y i + a 2 a2b2 = b2xi2 + a2yi2 - a2b2 + a2(1 + 2yi). Con lo que E(i+1) = E(i) + YChange(i) Entonces 2
XChange(i+1) = XChange(i) [+ 2b ] YChange(i+1) = YChange(i) + 2a E (i+1)
2
siempre
change(i)] = E (i) + YChange(i) [+ X
Donde lo que hay entre corchetes se cumple sólo si x i+1 = xi - 1. Por otro lado, podemos analizar la pendiente de la línea tangente a la elipse diferenciando la y respecto de la x en la ecuación (1), nos queda: 2b2x + 2a 2yy' = 0, despejando y':
El primer conjunto de puntos es aquel para el que y' < -1, es decir 2b2x >
2a2y. Si llamamos: 2
S topX(i)
= 2b x i,
S topY(i)
= 2a y i
2
Tenemos: Si xi+1 = xi - 1, entonces: y
StopX(i+1) = 2b2(xi - 1) = 2b2xi - 2b 2 = StopX(i)
- 2b2 y
StopY(i+1) = StopY(i) + 2a 2 siempre.
En resumen: S i
XChange + 2(E + YChange) < 0 enton ces x i +1 = x - 1. i
XChange = b 2 (1-2x i) 2
YChange = a (1+2y i) E
2
2
2
2
2
2
= b x + a y - a b i i
S topX
= 2b 2 x
S topY
= 2a 2 y
Cómo se calculan recursivamente:
XChange (i+1) = XChange(i)
2
[+ 2b ]
Y change (i+1) = YChange(i) + 2a E (i+1)
2
= E (i) + YChange(i) [+ XChange(i)] 2
S topX
(i+1) = S topX(i) [ -2b ]
S topY
(i+1) = S topY(i) + 2a
2
Los valores iniciales son: X = a, Y = 0. Por lo tanto: 2
XChange = b (1-2a) YChange = a 2 E
= 0
S topX
= 2b 2 a
S topY
= 0
Esta estructura produciría un código con dos bucles, aunque existe un
algoritmo que recorre un solo bucle, que usamos.
25.1.4. Algoritmo para dibujar circunferencias y círculos Una vez que tenemos el algoritmo para dibujar elipses, escribir el de las
circunferencias es sencillo, pues ésta es una elipse usando la razón de aspecto. Para dibujar el círculo, reusamos el código de la circunferencia, sólo que dibujamos una línea horizontal entre cada par de puntos encontrados en octantes paralelos.
25.1.5. Ejemplos ElipCM1 ElipCF1 ElipCN1
Código
Código
Código
[bin]
[bin]
[bin]
Resultado en pantalla
Source 25-03 - Elipses Bresenham
Podemos observar en la ilustración una elipse azul, una circunferencia verde, un círculo cián y un arco rojo.