Alguns Algoritmos Clássicos

Propaganda
Computação Gráfica
Walderson Shimokawa
46
4 Alguns Algoritmos Clássicos
Embora a programação seja uma atividade muito criativa, quase sempre requerendo que
(pequenos) problemas inteiramente novos sejam resolvidos, às vezes podemos nos beneficiar de
algoritmos bem conhecidos, publicados por outros e que geralmente fornecem soluções mais eficientes ou
mais elegantes do que aquelas que teríamos sido capazes de inventar nós mesmos. Com a computação
gráfica isso não é diferente. Esse capítulo é sobre alguns algoritmos gráficos bem conhecidos para (a)
calcular as coordenadas dos pixels que constituem retas e círculos, (b) recortar retas e polígonos e (c)
desenhar curvas suaves. Essas são as operações mais primitivas em computação gráfica e devem ser
executadas com a maior rapidez possível. Portanto, os algoritmos neste capítulo são otimizados para evitar
execuções demoradas, como multiplicações, divisões e cálculos de ponto flutuante.
4.1 O Algoritmo de Bresenham para o Desenho de Retas
Discutiremos agora como desenhar retas colocando pixels na tela. Apesar de, em Java, podermos
simplesmente usar o método drawLine sem nos preocuparmos com pixels, isso seria insatisfatório se não
soubéssemos como esse método funciona. Infelizmente, Java não possui um método com o único
propósito de colocar um pixel na tela, de modo que definimos o seguinte método, um tanto estranho, para
obtermos isso:
void putPixel(Graphics g, int x, int y)
{ g.drawLine(g, x, y, x, y);
}
Agora desenvolvemos um método drawLine da forma
void drawLine(Graphics g, int xP, int yP, int xQ, int yQ)
{ ...
}
que só usa o método putPixel anterior para a saída gráfica.
A Figura 30 mostra um segmento de reta com pontos extremos P(1, 1) e Q(12, 5), assim como os
pixels que temos que calcular para aproximar essa reta.
Figura 30: Pontos de grade aproximando um segmento de reta
A primeira versão a seguir do método drawLine é baseada no arredondamento do pixel mais
próximo, sendo 0,5 a margem de erro aceitável:
void drawLine1(Graphics g, int xP, int yP, int xQ, int yQ)
{ int x = xP, y = yP;
float d = 0, m = (float)(yQ - yP)/(float)(xQ - xP);
for (;;)
{ putPixel(g, x, y);
Computação Gráfica
Walderson Shimokawa
47
if (x == xQ) break;
x++;
d += m;
if (d >= 0.5) {y++; d--;};
}
}
A taxa de inclinação m e o erro d, presentes no código-fonte acima pode ser verificada na Figura 32, abaixo,
pois os pixels possuem endereçamento inteiro em vez de endereços baseados em números de ponto
flutuante (Figura 31).
Figura 31: Algoritmo incremental para definir a posição do próximo ponto
Figura 32: Inclinação m e erro d
Podemos melhorar o algoritmo do método drawLine trabalhando apenas com expressões inteiras:
void drawLine2(Graphics g, int xP, int yP, int xQ, int yQ)
{ int x = xP, y = yP, d = 0, dx = xQ – xP, c = 2 * dx,
m = 2 * (yQ - yP);
for (;;)
{ putPixel(g, x, y);
if (x == xQ) break;
x++;
d += m;
if (d >= dx) {y++; d -= c;};
}
}
Para retas que possuem ângulo de inclinação superior a 45, existe a necessidade de se trocar os
papéis de x e y para evitar que os pixels selecionados fiquem muito longes. Estas situações estão incluídas
no método geral de desenho de retas drawLine a seguir:
Computação Gráfica
Walderson Shimokawa
48
void drawLine(Graphics g, int xP, int yP, int xQ, int yQ)
{ int x = xP, y = yP, d = 0, dx = xQ – xP, dy = yQ – yP,
c, m, xInc = 1, yInc = 1;
if (dx < 0){xInc = -1; dx = -dx;}
if (dy < 0){yInc = -1; dy = -dy;}
if (dy <= dx)
{ c = 2 * dx; m = 2 * dy;
if (xInc < 0) dx++;
for (;;)
{ putPixel(g, x, y);
if (x == xQ) break;
x += xInc;
d += m;
if (d >= dx) {y += yInc; d -= c;};
}
}
else
{ c = 2 * dy; m = 2 * dx;
if (yInc < 0) dy++;
for (;;)
{ putPixel(g, x, y);
if (y == yQ) break;
y += yInc;
d += m;
if (d >= dy) {x += xInc; d -= c;};
}
}
}
A ideia de desenhar retas apenas usando variáveis inteiras foi primeiramente concebida por
Bresenham; seu nome é portanto associado a esse algoritmo.
4.2 Dobrando a Velocidade do Desenho de Retas
Como uma das operações gráficas básicas, o desenho de retas deve ser executado tão rapidamente
quanto possível. Na verdade, o hardware gráfico geralmente é avaliado pela velocidade na qual gera retas.
O algoritmo de retas de Bresenham é simples e eficiente na geração de retas, pois trabalha de forma
incremental calculando a posição do primeiro pixel a ser desenhado. Assim, ele itera tantas vezes quanto o
número de pixels a serem desenhados. O algoritmo de desenhos de retas em passo duplo de Rokne, Wyvill
e Wu (1990) objetiva a redução do número de iterações pela metade, calculando as posições dos próximos
dois pixels. Os quatro padrões de passo duplo possíveis são ilustrados na Figura 33:
Figura 33: Quatro padrões de passo duplo quando 0  inclinação  1
Os padrões 1 e 4 não podem ocorrer na mesma linha, como mostra a figura 34:
Figura 34: Escolha de padrões baseada no erro inicial d e na inclinação m
Computação Gráfica
Walderson Shimokawa
49
Como na seção anterior, ao discutirmos o algoritmo de Bresenham, começamos com um método
preliminar que ainda usa variáveis de ponto flutuante para torná-lo mais fácil de ser entendido, ainda que
não esteja otimizado tendo em vista a velocidade. Essa versão também funciona com retas desenhadas da
direita para a esquerda, ou seja, quando xQ < xP, assim como para retas com inclinação negativa. Todavia, o
valor absoluto da inclinação não deve ser maior que 1.
void doubleStep1(Graphics g, int xP, int yP, int xQ, int yQ)
{ int dx, dy, x, y, yInc;
if (xP >= xQ)
{ if (xP == xQ) // Não permitido porque dividimos por (dx = xQ - xP)
return;
// xP > xQ, então permute os pontos P e Q
int t;
t = xP; xP = xQ; xQ = t;
t = yP; yP = yQ; yQ = t;
}
// Agora xP < xQ
if (yQ >= yP){yInc = 1; dy = yQ - yP;} // Caso normal, yP < yQ
else
{yInc = -1; dy = yP - yQ;}
dx = xQ - xP; // dx > 0, dy > 0
float d = 0,
// Erro d = yexact - y
m = (float)dy/(float)dx; // m <= 1, m = |inclinação|
putPixel(g, xP, yP);
y = yP;
for (x=xP; x<xQ-1;)
{ if (d + 2 * m < 0.5) // Padrão 1:
{ putPixel(g, ++x, y);
putPixel(g, ++x, y);
d += 2 * m; // O erro aumenta em 2m, já que y permanece
// inalterado e yexact aumenta em 2m
}
else
if (d + 2 * m < 1.5) // Padrão 2 ou 3
{ if (d + m < 0.5) // Padrão 2
{ putPixel(g, ++x, y);
putPixel(g, ++x, y += yInc);
d += 2 * m - 1; // Devido a ++y, o erro é agora
// 1 a menos que com padrão 1
}
else // Padrão 3
{ putPixel(g, ++x, y += yInc);
putPixel(g, ++x, y);
d += 2 * m - 1; // Mesmo do padrão 2
}
}
else // Padrão 4:
{ putPixel(g, ++x, y += yInc);
putPixel(g, ++x, y += yInc);
d += 2 * m - 2; // Devido a y += 2, o erro é agora
// 2 a menos que com o padrão 1
}
}
if (x < xQ)
// x = xQ - 1
putPixel(g, xQ, yQ);
}
Uma versão mais eficiente da implementação para desenhar retas com padrões de passo duplo,
utilizando números inteiros em vez de ponto flutuante é apresentado a seguir, no método doubleStep2:
void doubleStep2(Graphics g, int xP, int yP, int xQ, int yQ)
{ int dx, dy, x, y, yInc;
Computação Gráfica
Walderson Shimokawa
50
if (xP >= xQ)
{ if (xP == xQ) // Não permitido porque dividimos por (dx = xQ - xP)
return;
int t;
// xP > xQ, então permute os pontos P e Q
t = xP; xP = xQ; xQ = t;
t = yP; yP = yQ; yQ = t;
}
// Agora xP < xQ
if (yQ >= yP){yInc = 1; dy = yQ - yP;}
else
{yInc = -1; dy = yP - yQ;}
dx = xQ - xP;
int dy4 = dy * 4, v = dy4 - dx, dx2 = 2 * dx, dy2 = 2 * dy,
dy4Minusdx2 = dy4 - dx2, dy4Minusdx4 = dy4Minusdx2 - dx2;
putPixel(g, xP, yP);
y = yP;
for (x=xP; x<xQ-1;)
{ if (v < 0)
// Equivalente a d + 2 * m < 0.5
{ putPixel(g, ++x, y);
// Padrão 1
putPixel(g, ++x, y);
v += dy4;
// Equivalente a d += 2 * m
}
else
if (v < dx2)
// Equivalente a d + 2 * m < 1.5
{ // Padrão 2 ou 3
if (v < dy2)
// Equivalente a d + m < 0.5
{ putPixel(g, ++x, y);
// Padrão 2
putPixel(g, ++x, y += yInc);
v += dy4Minusdx2;
// Equivalente a d += 2 * m - 1
}
else
{ putPixel(g, ++x, y += yInc); // Padrão 3
putPixel(g, ++x, y);
v += dy4Minusdx2;
// Equivalente a d += 2 * m - 1
}
}
else
{ putPixel(g, ++x, y += yInc); // Padrão 4
putPixel(g, ++x, y += yInc);
v += dy4Minusdx4;
// Equivalente a d += 2 * m - 2
}
}
if (x < xQ)
putPixel(g, xQ, yQ);
}
O método doubleStep2 acima só funciona se
−
≤
−
. Se desejar, o método acima
pode ser generalizado para trabalhar com qualquer reta, como foi feito com o algoritmo de Bresenham.
Da mesma forma que o algoritmo de Bresenham, o algoritmo passo duplo calcula apenas com
inteiros. Para retas longas ele tem um desempenho quase duas vezes melhor que o algoritmo de
Bresenham. Pode-se otimizá-lo ainda mais para se obter uma outra duplicação de velocidade aproveitandose da simetria em torno do ponto central de uma determinada reta.
4.3 Círculos
Nesta seção iremos ignorar a forma usual de se desenhar um círculo em Java por meio de
chamadas como
g.drawOval(xC – r, yC – r, 2 * r, 2 * r);
Computação Gráfica
Walderson Shimokawa
51
já que é nosso objetivo construirmos nós mesmos tal círculo, com centro C(xC, yC) e raio r, em que as
coordenadas de C e do raio são dadas como inteiros. Desenvolveremos um método na forma de:
void drawCircle(Graphics g, int xC, int yC, int r)
{ ...
}
que só usa o método putPixel da seção anterior como “primitiva gráfica” e que é uma implementação do
algoritmo de Bresenham para círculos. O círculo desenhado dessa forma será exatamente o mesmo
produzido pela chamada anterior drawOval. Em ambos os casos, x varia de xC – r a xC + r, incluindo esses
dois valores, de modo que 2r + 1 valores de r serão usados.
Assim como nas seções anteriores, começamos com um caso simples: usamos a origem do sistema
de coordenadas como o centro do círculo, e, dividindo o círculo em oito arcos de comprimento igual, nos
restringimos a um deles, o arco PQ. A equação deste círculo é:
x2 + y2 = r2
A Figura 35 mostra a situação, incluindo a grade de pixels, para o caso de r = 8. Começando pelo
topo no ponto P, com x = 0 e y = r, usaremos um laço no qual incrementamos x em 1 a cada passo; como na
seção anterior, precisamos de um teste para decidir se podemos deixar y sem alteração. Se esse não for o
caso, temos que decrementar y em 1.
Figura 35: Pixels que aproximam o arco PQ
Para tornar nosso algoritmo mais rápido, evitaremos calcular os quadrados x2 e y2, introduzindo
três novas variáveis inteiras não negativas u, v e E denotando as diferenças entre dois quadrados sucessivos
e o ‘erro’:
= ( − 1) −
=2 +1
=
− ( − 1) = 2 + 1
=
+
−
Agora podemos escrever o seguinte método para desenhar o arco PQ:
void arc8(Graphics g, int r)
{ int x = 0, y = r, u = 1, v = 2 * r - 1, e = 0;
while (x <= y)
{ putPixel(g, x, y);
x++; e += u; u += 2;
if (v < 2 * e) {y--; e -= v; v -= 2;}
}
}
Computação Gráfica
Walderson Shimokawa
52
O método arc8 é a base do nosso método final, drawCircle, listado a seguir. Além de desenhar um
círculo completo, ele também é mais geral do que arc8 por permitir que um ponto C arbitrário seja
especificado como o centro do círculo.
void drawCircle(Graphics g, int xC, int yC, int r)
{ int x = 0, y = r, u = 1, v = 2 * r - 1, E = 0;
while (x < y)
{ putPixel(g, xC + x, yC + y); // NNE
putPixel(g, xC + y, yC - x); // ESE
putPixel(g, xC - x, yC - y); // SSW
putPixel(g, xC - y, yC + x); // WNW
x++; E += u; u += 2;
if (v < 2 * E){y--; E -= v; v -= 2;}
if (x > y) break;
putPixel(g, xC + y, yC + x); // ENE
putPixel(g, xC + x, yC - y); // SSE
putPixel(g, xC - y, yC - x); // WSW
putPixel(g, xC - x, yC + y); // NNW
}
}
4.4 Recorte de Retas Cohen-Sutherland
Nesta seção discutiremos como desenhar segmentos de retas apenas quando estiverem dentro de
um determinado retângulo. As decisões lógicas necessárias para se descobrir que ações tomar tornam o
recorte de retas um tópico interessante do ponto de vista algorítmico. O algoritmo Cohen-Sutherland
resolve esse problema de uma forma elegante e eficiente. Expressaremos esse algoritmo em Java (classe
ClipLine):
import java.awt.*;
import java.awt.event.*;
import java.util.*;
public class ClipLine extends Frame
{ public static void main(String[] args){new ClipLine();}
ClipLine()
{ super("Clique em dois vértices opostos de um retângulo");
addWindowListener(new WindowAdapter()
{public void windowClosing(WindowEvent e){System.exit(0);}});
setSize(500, 300);
add("Center", new CvClipLine());
setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
show();
}
}
class CvClipLine extends Canvas
{ float xmin, xmax, ymin, ymax,
rWidth = 10.0F, rHeight = 7.5F, pixelSize;
int maxX, maxY, centerX, centerY, np=0;
CvClipLine()
{ addMouseListener(new MouseAdapter()
{ public void mousePressed(MouseEvent evt)
{ float x = fx(evt.getX()), y = fy(evt.getY());
if (np == 2) np = 0;
if (np == 0){xmin = x; ymin = y;}
else
{ xmax = x; ymax = y;
if (xmax < xmin)
Computação Gráfica
Walderson Shimokawa
53
{ float t = xmax; xmax = xmin; xmin = t;
}
if (ymax < ymin)
{ float t = ymax; ymax = ymin; ymin = t;
}
}
np++;
repaint();
}
});
}
void initgr()
{ Dimension d = getSize();
maxX = d.width - 1; maxY = d.height - 1;
pixelSize = Math.max(rWidth/maxX, rHeight/maxY);
centerX = maxX/2; centerY = maxY/2;
}
int iX(float
int iY(float
float fx(int
float fy(int
x){return
y){return
x){return
y){return
Math.round(centerX + x/pixelSize);}
Math.round(centerY - y/pixelSize);}
(x - centerX) * pixelSize;}
(centerY - y) * pixelSize;}
void drawLine(Graphics g, float xP, float yP,
float xQ, float yQ)
{ g.drawLine(iX(xP), iY(yP), iX(xQ), iY(yQ));
}
int clipCode(float x, float y)
{ return
(x < xmin ? 8 : 0) | (x > xmax ? 4 : 0) |
(y < ymin ? 2 : 0) | (y > ymax ? 1 : 0);
}
void clipLine(Graphics g,
float xP, float yP, float xQ, float yQ,
float xmin, float ymin, float xmax, float ymax)
{ int cP = clipCode(xP, yP), cQ = clipCode(xQ, yQ);
float dx, dy;
while ((cP | cQ) != 0)
{ if ((cP & cQ) != 0) return;
dx = xQ - xP; dy = yQ - yP;
if (cP != 0)
{ if ((cP & 8) == 8){yP += (xmin-xP) * dy / dx;
else
if ((cP & 4) == 4){yP += (xmax-xP) * dy / dx;
else
if ((cP & 2) == 2){xP += (ymin-yP) * dx / dy;
else
if ((cP & 1) == 1){xP += (ymax-yP) * dx / dy;
cP = clipCode(xP, yP);
}
else
if (cQ != 0)
{ if ((cQ & 8) == 8){yQ += (xmin-xQ) * dy / dx;
else
if ((cQ & 4) == 4){yQ += (xmax-xQ) * dy / dx;
else
if ((cQ & 2) == 2){xQ += (ymin-yQ) * dx / dy;
else
if ((cQ & 1) == 1){xQ += (ymax-yQ) * dx / dy;
cQ = clipCode(xQ, yQ);
}
xP = xmin;}
xP = xmax;}
yP = ymin;}
yP = ymax;}
xQ = xmin;}
xQ = xmax;}
yQ = ymin;}
yQ = ymax;}
Computação Gráfica
Walderson Shimokawa
54
}
drawLine(g, xP, yP, xQ, yQ);
}
public void paint(Graphics g)
{ initgr();
if (np == 1)
{ // Desenha linhas horizontais e verticais através
// do primeiro ponto definido:
drawLine(g, fx(0), ymin, fx(maxX), ymin);
drawLine(g, xmin, fy(0), xmin, fy(maxY));
} else
if (np == 2)
{ // Desenha retângulo:
drawLine(g, xmin, ymin, xmax, ymin);
drawLine(g, xmax, ymin, xmax, ymax);
drawLine(g, xmax, ymax, xmin, ymax);
drawLine(g, xmin, ymax, xmin, ymin);
// Desenha 20 pentágonos regulares concêntricos,
// desde que eles estejam localizados dentro do retângulo:
float rMax = Math.min(rWidth, rHeight)/2,
deltaR = rMax/20, dPhi = (float)(0.4 * Math.PI);
for (int j=1; j<=20; j++)
{ float r = j * deltaR;
// Desenha um pentágono:
float xA, yA, xB = r, yB = 0;
for (int i=1; i<=5; i++)
{ float phi = i * dPhi;
xA = xB; yA = yB;
xB = (float)(r * Math.cos(phi));
yB = (float)(r * Math.sin(phi));
clipLine(g, xA, yA, xB, yB, xmin, ymin, xmax, ymax);
}
}
}
}
}
O programa desenha 20 pentágonos concêntricos (regulares), desde que se localizem dentro de um
retângulo, que o usuário pode definir clicando em quaisquer dois vértices opostos. Quando ele clica pela
terceira vez, a situação é a mesma do início: a tela é limpa e um novo retângulo pode ser definido, no qual
novamente partes de 20 pentágonos aparecem, e assim por diante. Como de costume, se o usuário alterar
as dimensões da janela, o tamanho do desenho é alterado apropriadamente. A Figura 36 mostra a situação
logo após os pentágonos terem sido desenhados.
Figura 36: Demonstração do programa ClipLine.java
Computação Gráfica
Walderson Shimokawa
55
4.5 Recorde de Polígonos de Sutherland-Hodgman
Em contraste com o recorte de retas, discutido na seção anterior, agora lidaremos com o recorte de
polígonos, que é diferente pelo fato de converter um polígono em outro dentro de um determinado
retângulo, como as Figuras 37 e 38 ilustram.
Figura 37: Nove vértices do polígono definidos; o lado final ainda não está desenhado
Figura 38: Polígono completo e recortado
O programa que discutiremos desenha um retângulo fixo e permite ao usuário especificar os
vértices de um polígono clicando na mesma forma discutida na seção 1.5. Desde que o primeiro vértice, na
Figura 37 acima, não seja selecionado pela segunda vez, sucessivos vértices são conectados pelos lados do
polígono. Assim que o primeiro vértice é selecionado novamente, o polígono é recortado, como a Figura 38
mostra. Alguns vértices do polígono original não pertencem ao polígono recortado. Por outro lado, este
último polígono possui alguns vértices novos, que são todos pontos de interseção dos lados do polígono
original com os do retângulo. De modo geral, o número de vértices do polígono recortado pode ser maior,
igual ou menor que o do original. Na Figura 38 há cinco novos lados do polígono, que são parte dos lados
do retângulo.
Computação Gráfica
Walderson Shimokawa
56
O programa que produziu a Figura 38 é baseado no algoritmo de Sutherland-Hodgman, que
primeiro recorta todos os lados do polígono contra um lado do retângulo, ou melhor, a reta infinita através
de tal lado. Isso resulta em um novo polígono, que é então recortado contra o próximo lado do retângulo, e
assim por diante. A Figura 39 ilustra este processo. A Figura 40 mostra um retângulo e um polígono,
ABCDEF, gerando um novo polígono de saída IJKLFA.
Figura 39: Recorta o polígono dado contra um lado do retângulo por vez
Figura 40: Polígono ABCDEF recortado para IJKLFA
O programa a seguir (ClipPoly.java) mostra uma implementação em Java para executar o corte de
polígonos fora da área de desenho.
import java.awt.*;
import java.awt.event.*;
import java.util.*;
public class ClipPoly extends Frame
{ public static void main(String[] args){new ClipPoly();}
ClipPoly()
{ super("Defina os vértices do polígono clicando");
addWindowListener(new WindowAdapter()
{public void windowClosing(WindowEvent e){System.exit(0);}});
setSize(500, 300);
add("Center", new CvClipPoly());
setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
show();
}
}
Computação Gráfica
Walderson Shimokawa
class CvClipPoly extends Canvas
{ Poly poly = null;
float rWidth = 10.0F, rHeight = 7.5F, pixelSize;
int x0, y0, centerX, centerY;
boolean ready = true;
CvClipPoly()
{ addMouseListener(new MouseAdapter()
{ public void mousePressed(MouseEvent evt)
{ int x = evt.getX(), y = evt.getY();
if (ready)
{ poly = new Poly();
x0 = x; y0 = y;
ready = false;
}
if (poly.size() > 0 &&
Math.abs(x - x0) < 3 && Math.abs(y - y0) < 3)
ready = true;
else
poly.addVertex(new Point2D(fx(x), fy(y)));
repaint();
}
});
}
void initgr()
{ Dimension d = getSize();
int maxX = d.width - 1, maxY = d.height - 1;
pixelSize = Math.max(rWidth/maxX, rHeight/maxY);
centerX = maxX/2; centerY = maxY/2;
}
int iX(float
int iY(float
float fx(int
float fy(int
x){return
y){return
x){return
y){return
Math.round(centerX + x/pixelSize);}
Math.round(centerY - y/pixelSize);}
(x - centerX) * pixelSize;}
(centerY - y) * pixelSize;}
void drawLine(Graphics g, float xP, float yP, float xQ, float yQ)
{ g.drawLine(iX(xP), iY(yP), iX(xQ), iY(yQ));
}
void drawPoly(Graphics g, Poly poly)
{ int n = poly.size();
if (n == 0) return;
Point2D a = poly.vertexAt(n - 1);
for (int i=0; i<n; i++)
{ Point2D b = poly.vertexAt(i);
drawLine(g, a.x, a.y, b.x, b.y);
a = b;
}
}
public void paint(Graphics g)
{ initgr();
float xmin = -rWidth/3, xmax = rWidth/3,
ymin = -rHeight/3, ymax = rHeight/3;
// Desenha o retângulo de corte:
g.setColor(Color.blue);
drawLine(g, xmin, ymin, xmax, ymin);
drawLine(g, xmax, ymin, xmax, ymax);
drawLine(g, xmax, ymax, xmin, ymax);
drawLine(g, xmin, ymax, xmin, ymin);
g.setColor(Color.black);
if (poly == null) return;
57
Computação Gráfica
Walderson Shimokawa
int n = poly.size();
if (n == 0) return;
Point2D a = poly.vertexAt(0);
if (!ready)
{ // Mostra um pequeno retângulo em torno do primeiro vértice:
g.drawRect(iX(a.x)-2, iY(a.y)-2, 4, 4);
// Desenha polígono incompleto:
for (int i=1; i<n; i++)
{ Point2D b = poly.vertexAt(i);
drawLine(g, a.x, a.y, b.x, b.y);
a = b;
}
} else
{ poly.clip(xmin, ymin, xmax, ymax);
drawPoly(g, poly);
}
}
}
class Poly
{ Vector v = new Vector();
void addVertex(Point2D p){v.addElement(p);}
int size(){return v.size();}
Point2D vertexAt(int i)
{ return (Point2D)v.elementAt(i);
}
void clip(float xmin, float ymin, float xmax, float ymax)
{ // Recorte de polígono de Sutherland-Hodgman:
Poly poly1 = new Poly();
int n;
Point2D a, b;
boolean aIns, bIns; // se A ou B estiver no mesmo
//
lado que o retângulo
// Corte contra x == xmax:
if ((n = size()) == 0) return;
b = vertexAt(n-1);
for (int i=0; i<n; i++)
{ a = b; b = vertexAt(i);
aIns = a.x <= xmax; bIns = b.x <= xmax;
if (aIns != bIns)
poly1.addVertex(new Point2D(xmax, a.y +
(b.y - a.y) * (xmax - a.x)/(b.x - a.x)));
if (bIns) poly1.addVertex(b);
}
v = poly1.v; poly1 = new Poly();
// Corte contra x == xmin:
if ((n = size()) == 0) return;
b = vertexAt(n-1);
for (int i=0; i<n; i++)
{ a = b; b = vertexAt(i);
aIns = a.x >= xmin; bIns = b.x >= xmin;
if (aIns != bIns)
poly1.addVertex(new Point2D(xmin, a.y +
(b.y - a.y) * (xmin - a.x)/(b.x - a.x)));
if (bIns) poly1.addVertex(b);
}
v = poly1.v; poly1 = new Poly();
// Corte contra y == ymax:
if ((n = size()) == 0) return;
b = vertexAt(n-1);
58
Computação Gráfica
Walderson Shimokawa
59
for (int i=0; i<n; i++)
{ a = b; b = vertexAt(i);
aIns = a.y <= ymax; bIns = b.y <= ymax;
if (aIns != bIns)
poly1.addVertex(new Point2D(a.x +
(b.x - a.x) * (ymax - a.y)/(b.y - a.y), ymax));
if (bIns) poly1.addVertex(b);
}
v = poly1.v; poly1 = new Poly();
// Corte contra y == ymin:
if ((n = size()) == 0) return;
b = vertexAt(n-1);
for (int i=0; i<n; i++)
{ a = b; b = vertexAt(i);
aIns = a.y >= ymin;
bIns = b.y >= ymin;
if (aIns != bIns)
poly1.addVertex(new Point2D(a.x +
(b.x - a.x) * (ymin - a.y)/(b.y - a.y), ymin));
if (bIns) poly1.addVertex(b);
}
v = poly1.v; poly1 = new Poly();
}
}
O algoritmo de Sutherland-Hodgman pode ser adaptado para o recorte de outras áreas que não
retângulos e para aplicações tridimensionais.
4.6 Curvas de Bézier
Há muitos algoritmos para desenhar curvas. Um especialmente elegante e prático é baseado na
especificação de quatro pontos que determinam totalmente um segmento de curva: dois pontos extremos
e dois pontos de controle. Curvas desenhadas dessa forma são chamadas de curvas (cúbicas) de Bézier. Na
Figura 41, temos os pontos extremos P0 e P3, os pontos de controle P1 e P2, e a curva desenhada com base
nesses quatro pontos.
Figura 41: Curva de Bézier baseada em quatro pontos
Escrever um método para desenhar essa curva é surpreendentemente fácil, desde que usemos
recursão. Como mostra a Figura 42, calculamos seis pontos médios, a saber:




A, o ponto médio de P0P1
B, o ponto médio de P2P3
C, o ponto médio de P1P2
A1, o ponto médio de AC
Computação Gráfica


Walderson Shimokawa
60
B1, o ponto médio de BC
C1, o ponto médio de A1B1
Figura 42: Definindo os pontos para dois segmentos de curva menores
Após isso, podemos dividir a tarefa original de desenhar a curva de Bézier P0P3 (com pontos de
controle P1 e P2) em duas tarefas mais simples:


Desenhar a curva de Bézier P0C1, com pontos de controle A e A1
Desenhar a curva de Bézier C1P3, com pontos de controle B1 e B
O método recursivo bezier no programa a seguir mostra uma implementação desse algoritmo. O
programa espera que o usuário especifique quatro pontos P0, P1, P2 e P3, nessa ordem, clicando com o
mouse. Após o quarto ponto, P3, ter sido especificado, a curva é desenhada. Qualquer novo clique do
mouse é interpretado como o primeiro ponto P0 de uma nova curva; a curva anterior simplesmente
desaparece e outra curva pode ser construída da mesma maneira que a primeira, e assim por diante.
import java.awt.*;
import java.awt.event.*;
import java.util.*;
public class Bezier extends Frame
{ public static void main(String[] args){new Bezier();}
Bezier()
{ super("Defina os pontos extremos e de controle do segmento");
addWindowListener(new WindowAdapter()
{public void windowClosing(WindowEvent e){System.exit(0);}});
setSize(500, 300);
add("Center", new CvBezier());
setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
show();
}
}
class CvBezier extends Canvas
{ Point2D[] p = new Point2D[4];
int np = 0, centerX, centerY;
float rWidth = 10.0F, rHeight = 7.5F, eps = rWidth/100F, pixelSize;
CvBezier()
{ addMouseListener(new MouseAdapter()
{ public void mousePressed(MouseEvent evt)
{ float x = fx(evt.getX()), y = fy(evt.getY());
if (np == 4) np = 0;
p[np++] = new Point2D(x, y);
repaint();
}
Computação Gráfica
Walderson Shimokawa
61
});
}
void initgr()
{ Dimension d = getSize();
int maxX = d.width - 1, maxY = d.height - 1;
pixelSize = Math.max(rWidth/maxX, rHeight/maxY);
centerX = maxX/2; centerY = maxY/2;
}
int iX(float x){return Math.round(centerX + x/pixelSize);}
int iY(float y){return Math.round(centerY - y/pixelSize);}
float fx(int x){return (x - centerX) * pixelSize;}
float fy(int y){return (centerY - y) * pixelSize;}
Point2D middle(Point2D a, Point2D b)
{ return new Point2D((a.x + b.x)/2, (a.y + b.y)/2);
}
void bezier(Graphics g, Point2D p0, Point2D p1,
Point2D p2, Point2D p3)
{ int x0 = iX(p0.x), y0 = iY(p0.y),
x3 = iX(p3.x), y3 = iY(p3.y);
if (Math.abs(x0 - x3) <= 1 && Math.abs(y0 - y3) <= 1)
g.drawLine(x0, y0, x3, y3);
else
{ Point2D a = middle(p0, p1), b = middle(p3, p2),
c = middle(p1, p2), a1 = middle(a, c),
b1 = middle(b, c), c1 = middle(a1, b1);
bezier(g, p0, a, a1, c1);
bezier(g, c1, b1, b, p3);
}
}
public void paint(Graphics g)
{ initgr();
int left = iX(-rWidth/2), right = iX(rWidth/2),
bottom = iY(-rHeight/2), top = iY(rHeight/2);
g.drawRect(left, top, right - left, bottom - top);
for (int i=0; i<np; i++)
{ // Mostra pequeno retângulo em torno do ponto:
g.drawRect(iX(p[i].x)-2, iY(p[i].y)-2, 4, 4);
if (i > 0)
// Desenha reta p[i-1]p[i]:
g.drawLine(iX(p[i-1].x), iY(p[i-1].y),
iX(p[i].x), iY(p[i].y));
}
if (np == 4) bezier(g, p[0], p[1], p[2], p[3]);
}
}
Como esse programa usa o modo de mapeamento isotrópico com intervalo de coordenadas lógicas
0-10,0 para x e 0-7,5 para y, só devemos usar um retângulo cuja altura seja 75% de sua largura. Como nas
seções 1.4 e 1.5, colocamos esse retângulo no centro da tela e o tornamos o maior possível. Ele é mostrado
na Figura 43; se os quatro pontos para a curva forem escolhidos dentro desse retângulo, eles serão visíveis
independentemente de como o tamanho da janela é alterado pelo usuário. O mesmo se aplica à curva, que
é automaticamente escalada, da mesma forma que fizemos nas seções 1.4 e 1.5.
Computação Gráfica
Walderson Shimokawa
62
Figura 43: Uma curva de Bézier desenhada
Como métodos recursivos podem gastar muitos recursos computacionais, podemos substituir o
método recursivo bezier, do programa apresentado acima, pelo seguinte método não-recursivo:
void bezier1(Graphics g, Point2D[] p)
{ int n = 200;
float dt = 1.0F/n, x = p[0].x, y = p[0].y, x0, y0;
for (int i=1; i<=n; i++)
{ float t = i * dt, u = 1 - t,
tuTriple = 3 * t * u,
c0 = u * u * u,
c1 = tuTriple * u,
c2 = tuTriple * t,
c3 = t * t * t;
x0 = x; y0 = y;
x = c0*p[0].x + c1*p[1].x + c2*p[2].x + c3*p[3].x;
y = c0*p[0].y + c1*p[1].y + c2*p[2].y + c3*p[3].y;
g.drawLine(iX(x0), iY(y0), iX(x), iY(y));
}
}
Este método produz a mesma curva que a produzida por bezier, desde que também substituamos a
chamada a bezier por esta:
bezier1(g, p);
Considerando que B(t) denota a posição no tempo t, a derivada B’(t) dessa função (que também é
um vetor coluna em t) pode ser considerada a velocidade. Após alguma manipulação algébrica, a derivação
resulta em:
B’(t) = -3(t - 1)2P0 + 3(3t - 1)(t - 1)P1 – 3t(3t - 2)(t -1)P2 + 3t2P3
o que dá:
B’(0) = 3(P1 – P0)
B’(1) = 3(P3 – P2)
Esses dois resultados são os vetores de velocidade no ponto inicial P0 e no ponto final P3. Eles mostram que
a direção do movimento nesses pontos é a mesma que a dos vetores P0P1 e P2P3, como a Figura 44 ilustra.
Computação Gráfica
Walderson Shimokawa
63
Figura 44: Velocidade nos pontos P0 e P3
Discutimos duas formas inteiramente diferentes de desenhar uma curva entre os pontos P0 e P3, e
sem um experimento não fica claro que essas curvas são idênticas. Por enquanto, faremos distinção entre
as duas curvas e as chamaremos de:


Curva do ponto médio: desenhada por um processo recursivo de cálculo dos pontos médios e
implementada no método bezier;
Curva analítica: Dara pela equação considerando o tempo, calculando a velocidade dos vetores,
implementada no método bezier1.
4.6.1
Desenhando Curvas Suaves a Partir de Segmentos de Curvas
Suponha que queiramos combinar dois segmentos de curvas de Bézier, um baseado nos quatro
pontos P0, P1, P2 e P3 e o outro nos pontos Q0, Q1, Q2 e Q3, de forma que o ponto final P3 do primeiro
segmento coincida com o ponto inicial Q0 do segundo. A curva resultante será mais suave se a velocidade
final B’(1) (veja a Figura 44) do primeiro segmento for igual à velocidade inicial B’(0) do segundo. Esse será
o caso se o ponto P3 (=Q0) estiver exatamente no meio do segmento de reta P2Q1. O alto grau de suavidade
obtido dessa forma é chamado de continuidade de segunda ordem. Isso implica não apenas que os dois
segmentos possuem a mesma tangente no seu ponto comum P3 = Q0, mas também que a curvatura é
contínua nesse ponto. Em contraste, temos continuidade de primeira ordem se P3 se localizar no segmento
de reta P2Q1 mas não no meio dele. Nesse caso, embora a curva pareça razoavelmente suave porque
ambos os segmentos possuem a mesma tangente no ponto comum P3 = Q0, há uma descontinuidade na
curvatura nesse ponto. A Figura 45 ilustra esta a suavização de curvas discutidas aqui.
Figura 45: Curvas suaves através de segmentos de curva
4.6.2
Notação Matricial
A equação apresentada e discutida anteriormente pode ser reduzida para:
( ) = (−
+3
+3
+
)
+ 3(
−2
+
)
− 3(
−
) +
Isso é interessante porque nos fornece uma forma muito eficiente de desenhar um segmento de
curva de Bézier, como nos mostra o seguinte método melhorado:
Computação Gráfica
Walderson Shimokawa
64
void bezier2(Graphics g, Point2D[] p)
{ int n = 200;
float dt = 1.0F/n,
cx3 = -p[0].x + 3 * (p[1].x - p[2].x) + p[3].x,
cy3 = -p[0].y + 3 * (p[1].y - p[2].y) + p[3].y,
cx2 = 3 * (p[0].x - 2 * p[1].x + p[2].x),
cy2 = 3 * (p[0].y - 2 * p[1].y + p[2].y),
cx1 = 3 * (p[1].x - p[0].x),
cy1 = 3 * (p[1].y - p[0].y),
cx0 = p[0].x, cy0 = p[0].y,
x = p[0].x, y = p[0].y, x0, y0;
for (int i=1; i<=n; i++)
{ float t = i * dt;
x0 = x; y0 = y;
x = ((cx3 * t + cx2) * t + cx1) * t + cx0;
y = ((cy3 * t + cy2) * t + cy1) * t + cy0;
g.drawLine(iX(x0), iY(y0), iX(x), iY(y));
}
}
Embora bezier2 não pareça mais simples que bezier1, é muito mais eficiente devido ao número
reduzido de operações aritméticas no laço for. Com um grande número de passos, como n = 200 nessas
versões de bezier1 e bezier2, é o número de operações dentro do laço que conta, e não as ações
preparatórias que precedem o laço.
4.6.3
Curvas 3D
Embora as curvas discutidas aqui sejam bidimensionais, curvas tridimensionais podem ser geradas
da mesma forma. Simplesmente adicionamos um componente z a B(t) e aos pontos de controle, que será
calculado da mesma forma que os componentes x e y.
4.7 Ajuste de Curvas B-Spline
Além das técnicas discutidas na seção anterior, há outras formas de gerar curvas x = f(t) e y = g(t),
em que f e g são polinômios de terceiro grau em t. Uma técnica popular, conhecida como B-Splines, possui
a característica de que a curva gerada normalmente não passa pelos pontos dados. Chamaremos todos
esses pontos de pontos de controle. Um único segmento de tal curva, baseado em quatro pontos de
controle A, B, C e D, parece bastante desapontador pois dá a impressão de estar relacionado apenas a B e
C. Isso é mostrado na Figura 46, na qual, da esquerda para a direita, os pontos A, B, C e D estão marcados
novamente com pequenos quadrados.
Figura 46: Segmento B-Spline único, baseado em quatro pontos
Computação Gráfica
Walderson Shimokawa
65
Todavia, um ponto forte a favor de B-Splines é que essa técnica facilita o desenho de curvas muito
suaves que consistem em muitos segmentos de curva. Como você pode ver na Figura 47, a curva é de fato
bastante suave: temos continuidade de segunda ordem, conforme discutido na seção anterior. Lembre-se
de que isso implica que até a curvatura é contínua nos pontos em que dois segmentos de curva adjacentes
se encontram. Como mostra a parte da curva próxima do vértice inferior direito, podemos tornar a
distância entre uma curva e os pontos dados muito pequena ao fornecer diversos pontos próximos uns dos
outros. A Figura 47 abaixo teve o seu primeiro ponto de controle iniciado na parte inferior esquerda,
seguindo para a parte superior esquerda, continuando nos pontos seguintes, no sentido horário, voltando
ao primeiro e segundo pontos marcados novamente (note que o segmento de reta à esquerda parece estar
mais grossa).
Figura 47: Curva B-Spline consistindo em cinco segmentos de curva
A equação abaixo serve para definir as curvas B-Spline:
( ) = (−
+3
−3
+
)
+ (
−2
+
)
+ (−
+
) + (
+4
+
)
O programa a seguir se baseia nessa equação. O usuário pode clicar qualquer número de pontos,
que são usados como os pontos P0, P1, ..., Pn-1. O primeiro segmento de curva aparece imediatamente após
o quarto ponto de controle, P3, ter sido definido, e cada ponto de controle adicional faz com que um novo
segmento de curva apareça. Para mostrar apenas a curva, o usuário pode pressionar qualquer tecla, o que
também termina o processo de entrada. Após isso, podemos gerar outra curva clicando o mouse
novamente. As Figuras 46 e 47 foram produzidas por este programa:
import java.awt.*;
import java.awt.event.*;
import java.util.*;
public class Bspline extends Frame
{ public static void main(String[] args){new Bspline();}
Bspline()
{ super("Defina pontos: pressione qualquer tecla após o final");
addWindowListener(new WindowAdapter()
{public void windowClosing(WindowEvent e){System.exit(0);}});
setSize(500, 300);
add("Center", new CvBspline());
Computação Gráfica
Walderson Shimokawa
setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
show();
}
}
class CvBspline extends Canvas
{ Vector V = new Vector();
int np = 0, centerX, centerY;
float rWidth = 10.0F, rHeight = 7.5F, eps = rWidth/100F, pixelSize;
boolean ready = false;
CvBspline()
{ addMouseListener(new MouseAdapter()
{ public void mousePressed(MouseEvent evt)
{ float x = fx(evt.getX()), y = fy(evt.getY());
if (ready)
{ V.removeAllElements();
np = 0;
ready = false;
}
V.addElement(new Point2D(x, y));
np++;
repaint();
}
});
addKeyListener(new KeyAdapter()
{ public void keyTyped(KeyEvent evt)
{ evt.getKeyChar();
if (np >= 4) ready = true;
repaint();
}
});
}
void initgr()
{ Dimension d = getSize();
int maxX = d.width - 1, maxY = d.height - 1;
pixelSize = Math.max(rWidth/maxX, rHeight/maxY);
centerX = maxX/2; centerY = maxY/2;
}
int iX(float x){return Math.round(centerX + x/pixelSize);}
int iY(float y){return Math.round(centerY - y/pixelSize);}
float fx(int x){return (x - centerX) * pixelSize;}
float fy(int y){return (centerY - y) * pixelSize;}
void bspline(Graphics g, Point2D[] p)
{ int m = 50, n = p.length;
float xA, yA, xB, yB, xC, yC, xD, yD,
a0, a1, a2, a3, b0, b1, b2, b3, x=0, y=0, x0, y0;
boolean first = true;
for (int i=1; i<n-2; i++)
{ xA=p[i-1].x; xB=p[i].x; xC=p[i+1].x; xD=p[i+2].x;
yA=p[i-1].y; yB=p[i].y; yC=p[i+1].y; yD=p[i+2].y;
a3=(-xA+3*(xB-xC)+xD)/6; b3=(-yA+3*(yB-yC)+yD)/6;
a2=(xA-2*xB+xC)/2;
b2=(yA-2*yB+yC)/2;
a1=(xC-xA)/2;
b1=(yC-yA)/2;
a0=(xA+4*xB+xC)/6;
b0=(yA+4*yB+yC)/6;
for (int j=0; j<=m; j++)
{ x0 = x;
y0 = y;
float t = (float)j/(float)m;
x = ((a3*t+a2)*t+a1)*t+a0;
y = ((b3*t+b2)*t+b1)*t+b0;
66
Computação Gráfica
Walderson Shimokawa
67
if (first) first = false;
else
g.drawLine(iX(x0), iY(y0), iX(x), iY(y));
}
}
}
public void paint(Graphics g)
{ initgr();
int left = iX(-rWidth/2), right = iX(rWidth/2),
bottom = iY(-rHeight/2), top = iY(rHeight/2);
g.drawRect(left, top, right - left, bottom - top);
Point2D[] p = new Point2D[np];
V.copyInto(p);
if (!ready)
{ for (int i=0; i<np; i++)
{ // Mostra pequeno retângulo em torno do ponto:
g.drawRect(iX(p[i].x)-2, iY(p[i].y)-2, 4, 4);
if (i > 0)
// Desenha reta p[i-1]p[i]:
g.drawLine(iX(p[i-1].x), iY(p[i-1].y),
iX(p[i].x), iY(p[i].y));
}
}
if (np >= 4) bspline(g, p);
}
}
Para ver porque B-Splines são tão suaves, você deve derivar B(t) duas vezes e verificar que, para
qualquer segmento que não seja o final, os valores de B(1), B’(1) e B’’(1) no ponto final desses segmentos
são iguais aos valores B(0), B’(0) e B’’(0) no ponto inicial do próximo segmento de curva. Por exemplo, para
a continuidade da própria curva, encontramos
(1) = (−
+3
−3
= (
+4
+
+
)
+ (
−2
+
)
+ (−
+
) + (
+4
+
)
)
para o primeiro segmento, baseado em P0, P1, P2 e P3, enquanto podemos ver imediatamente que obtemos
exatamente esse valor se calcularmos B(0) para o segundo segmento de curva, baseado em P1, P2, P3 e P4.
4.8 Exercício
Como pixels normais são muito pequenos, eles não mostram muito claramente quais deles são
selecionados pelos algoritmos de Bresenham. Use uma grade de pontos para simular uma tela com
resolução muito baixa e demonstrar tanto o método drawLine da seção 4.1 (com g como seu primeiro
argumento) quanto o método drawCircle da seção 4.3. Apenas os pontos da grade devem ser usados como
centros dos “superpixels”. Codifique um novo método putPixel para desenhar um pequeno círculo como tal
centro, tendo como diâmetro a distância dGrid entre dois pontos vizinhos da grade.
Não altere os métodos drawLine e drawCircle que desenvolvemos, mas use a distância dGrid, entre
dois pontos vizinhos da grade, no novo método putPixel diferente do mostrado no início da seção 4.1. A
Figura 48 mostra uma grade (com dGrid = 10) e uma reta e um círculo desenhados dessa forma. Assim
como na Figura 30, a reta mostrada aqui tem pontos extremos P(1, 1) e Q(12, 5), mas dessa vez o eixo y
positivo aponta para baixo e a origem é o vértice superior esquerdo do retângulo de desenho. O círculo
possui raio r = 8 e é aproximado pelos mesmos pixels que os mostrados na figura 35 para um oitavo desse
círculo. A reta e o círculo foram produzidos pelos seguintes chamadas aos métodos drawLine e drawCircle
das seções 4.1 e 4.3 (mas com um método putPixel diferente):
Computação Gráfica
Walderson Shimokawa
68
drawLine(g, 1, 1, 12, 5); //g, xP, yP, xQ, yQ
drawCircle(g, 23, 10, 8); //g, xC, yC, r
Figura 48: Algoritmos de Bresenham para uma reta e para um círculo
4.9 Resposta do Exercício
O programa a seguir produz apenas a Figura 48. Você deve estendê-lo, permitindo ao usuário
especificar os dois pontos extremos de um segmento de reta e tanto o centro quanto o raio do círculo.
import java.awt.*;
import java.awt.event.*;
import java.util.*;
public class Bresenham extends Frame
{ public static void main(String[] args){new Bresenham();}
Bresenham()
{ super("Bresenham");
addWindowListener(new WindowAdapter()
{public void windowClosing(WindowEvent e){System.exit(0);}});
setSize(340, 230);
add("Center", new CvBresenham());
show();
}
}
class CvBresenham extends Canvas
{ float rWidth = 10.0F, rHeight = 7.5F, pixelSize;
int centerX, centerY, dGrid = 10, maxX, maxY;
void initgr()
{ Dimension d;
d = getSize();
maxX = d.width - 1;
maxY = d.height - 1;
pixelSize = Math.max(rWidth/maxX, rHeight/maxY);
centerX = maxX/2; centerY = maxY/2;
}
int iX(float x){return Math.round(centerX + x/pixelSize);}
int iY(float y){return Math.round(centerY - y/pixelSize);}
void putPixel(Graphics g, int x, int y)
{ int x1 = x * dGrid, y1 = y * dGrid, h = dGrid/2;
g.drawOval(x1 - h, y1 - h, dGrid, dGrid);
}
void drawLine(Graphics g, int xP, int yP, int xQ, int yQ)
Computação Gráfica
{
Walderson Shimokawa
int x = xP, y = yP, D = 0, HX = xQ - xP, HY = yQ - yP,
c, M, xInc = 1, yInc = 1;
if (HX < 0){xInc = -1; HX = -HX;}
if (HY < 0){yInc = -1; HY = -HY;}
if (HY <= HX)
{ c = 2 * HX; M = 2 * HY;
for (;;)
{ putPixel(g, x, y);
if (x == xQ) break;
x += xInc;
D += M;
if (D > HX){y += yInc; D -= c;}
}
}
else
{ c = 2 * HY; M = 2 * HX;
for (;;)
{ putPixel(g, x, y);
if (y == yQ) break;
y += yInc;
D += M;
if (D > HY){x += xInc; D -= c;}
}
}
}
void drawCircle(Graphics g, int xC, int yC, int r)
{ int x = 0, y = r, u = 1, v = 2 * r - 1, E = 0;
while (x < y)
{ putPixel(g, xC + x, yC + y); // NNE
putPixel(g, xC + y, yC - x); // ESE
putPixel(g, xC - x, yC - y); // SSW
putPixel(g, xC - y, yC + x); // WNW
x++; E += u; u += 2;
if (v < 2 * E){y--; E -= v; v -= 2;}
if (x > y) break;
putPixel(g, xC + y, yC + x); // ENE
putPixel(g, xC + x, yC - y); // SSE
putPixel(g, xC - y, yC - x); // WSW
putPixel(g, xC - x, yC + y); // NNW
}
}
void showGrid(Graphics g)
{ for (int x=dGrid; x<=maxX; x+=dGrid)
for (int y=dGrid; y<=maxY; y+=dGrid)
g.drawLine(x, y, x, y);
}
public void paint(Graphics g)
{ initgr();
showGrid(g);
drawLine(g, 1, 1, 12, 5);
drawCircle(g, 23, 10, 8);
}
}
69
Download