Escolar Documentos
Profissional Documentos
Cultura Documentos
04-Alguns-Algoritmos-de Computação Gráfica Classicos PDF
04-Alguns-Algoritmos-de Computação Gráfica Classicos PDF
void drawLine(Graphics g, int xP, int yP, int xQ, int yQ)
{ ...
}
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.
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).
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.
Os padrões 1 e 4 não podem ocorrer na mesma linha, como mostra a figura 34:
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);
}
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 aproveitando-
se 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:
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.
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
= + −
import java.awt.*;
import java.awt.event.*;
import java.util.*;
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();
}
}
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;
}
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; xP = xmin;}
else
if ((cP & 4) == 4){yP += (xmax-xP) * dy / dx; xP = xmax;}
else
if ((cP & 2) == 2){xP += (ymin-yP) * dx / dy; yP = ymin;}
else
if ((cP & 1) == 1){xP += (ymax-yP) * dx / dy; yP = ymax;}
cP = clipCode(xP, yP);
}
else
if (cQ != 0)
{ if ((cQ & 8) == 8){yQ += (xmin-xQ) * dy / dx; xQ = xmin;}
else
if ((cQ & 4) == 4){yQ += (xmax-xQ) * dy / dx; xQ = xmax;}
else
if ((cQ & 2) == 2){xQ += (ymin-yQ) * dx / dy; yQ = ymin;}
else
if ((cQ & 1) == 1){xQ += (ymax-yQ) * dx / dy; yQ = ymax;}
cQ = clipCode(xQ, yQ);
}
Computação Gráfica Walderson Shimokawa 54
}
drawLine(g, xP, yP, xQ, yQ);
}
Figura 37: Nove vértices do polígono definidos; o lado final ainda não está desenhado
Figura 39: Recorta o polígono dado contra um lado do retângulo por vez
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.*;
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;
}
void drawLine(Graphics g, float xP, float yP, float xQ, float yQ)
{ g.drawLine(iX(xP), iY(yP), iX(xQ), iY(yQ));
}
Point2D vertexAt(int i)
{ return (Point2D)v.elementAt(i);
}
O algoritmo de Sutherland-Hodgman pode ser adaptado para o recorte de outras áreas que não
retângulos e para aplicações tridimensionais.
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:
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:
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.*;
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();
}
}
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;
}
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
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:
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á:
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
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.
( ) = (− +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.
( ) = (− +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.*;
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 66
setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
show();
}
}
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;}
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 + ) + ( −2 + ) + (− + ) + ( +4 + )
= ( +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
import java.awt.*;
import java.awt.event.*;
import java.util.*;
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;
}
void drawLine(Graphics g, int xP, int yP, int xQ, int yQ)
Computação Gráfica Walderson Shimokawa 69
{ 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 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);
}