Rysowanie figur

PSzczepan

Podstawową rzeczą od której trzeba zacząć jest ręczne wyrysowanie okręgu- jeżeli chcemy narysować figurę o wszystkich bokach równej długości, najłatwiej będzie wpisać ją w okrąg. Oczywiście okrąg można narysować za pomocą jednej procedury, ale to nie rozwiązuje problemu. Potrzebna procedura wygląda tak:

var x,y:integer;
i:real ;

// ...

repeat
  x:=round(sin(i)*40); {40 to promień okręgu};
  y:=round(cos(i)*40);
  i:=i+0.001;
 {wartość o jaką zmienia się kąt w radianach w każdym cyklu
 pętli- mniejsza wartość wolniejsze działanie, ale większa dokładność}
  putpixel(x,y,10); {rysowanie}
until  i>2*pi ;

Jak łatwo zauważyć łatwo ją zmodyfikować tak, aby rysowana była elipsa:

x:=round(sin(i)*40); {40 to promień poziomy};
y:=round(cos(i)*80);{80- pionowy}

O ile elipsę można w Pascalu narysować poleceniem ellipse, o tyle jeżeli chcemy żeby była pochylona o jakiś kąt, musimy sami wszysko zaprogramować. Aby uzyskać ten efekt należy skorzystać z równań obracających dany punkt wokół osi:

xz:=pi; {kąt o jaki elipsa ma być pochylona}
repeat

  x:=round(sin(i)*40);
  y:=round(cos(i)*80); {współrzędne normalnej elipsy}
  x1:=round(cos(xz)*x+cos(xz+90)*y)+200;
 {wartość 200 spowoduje rysowaniu na środku (mniej więcej) ekranu, 
zamiast w górnym lewym rogu}
  y1:=round(sin(xz)*x+sin(xz+90)*y)+200; {x1,y1- współrzędne pochylone}
  i:=i+0.001;
  putpixel(x1,y1,10);

until i>2*pi;

Mając te podstawy możemy narysować teraz np.: pięciokąt foremny. Nie musimy do tego rysować okręgu wystarczy, że wyliczymy 5 punktów na okręgu znajdujących się w równej odległości od siebie:

var pent:array [1..5] of array [1..2] of integer;

// ...

for x:=1 to 5 do begin 
  pent[x,1]:=round(sin(2*pi/5*x)*40)+200;  
  pent[x,2]:=round(cos(2*pi/5*x))*40)+200;
end;

moveto(pent[1,1],pent[1,2]);

for x:=2 to 5 do lineto(pent[x,1],pent[x,2]);

lineto(pent[1,1],pent[1,1]);

Jak widać ten sam pięciokąt możemy równie dobrze wpisać w elipsę (nie będzie wtedy foremny oczywiście), a nawet w pochyloną elipsę:

z:=pi;

for x:=1 to 5 do begin 

  x1:=round(sin(2*pi/5*x)*40);  
  y1:=round(cos(2*pi/5*x)*90);
  pent[x,2]:=round(sin(z)*x1+sin(z+90)*y1)+200;
  pent[x,1]:=round(cos(z)*x1+cos(z+90)*y1)+200;

end;

Na koniec jeszcze jeden ciekawy drobiazg, a mianowicie rysowanie pentagramu. Znając już powyższe reguły nie ma w tym nic trudnego- wystarczy narysować okrąg, wyliczyć pięć punktów jak w pięciokącie, jedynie linie trzeba rysować do co drugiego punktu:

moveto(pent[1,1],pent[1,2]);
lineto(pent[3,1],pent[3,2]);
lineto(pent[5,1],pent[5,2]);
lineto(pent[2,1],pent[2,2]);
lineto(pent[4,1],pent[4,2]);
lineto(pent[1,1],pent[1,2]);

Problem z tak narysowanym pentagramem jest taki, że jest on odwrócony. Najprościej przywrócić go do normalnej pozycji poprzez wyliczenie punktów zaczynając od punktu znajdującego się po przeciwległej stronie okręgu:

pent[x,1]:=round(sin(2*pi/5*x+pi)*40);  
pent[x,2]:=round(cos(2*pi/5*x+pi)*40);

To teraz pozostaje nam już tylko rysować pochylone pentagramy :)

Piotr Szczepaniec
[email protected]
Serwis OHP

5 komentarzy

Rysowanie elipsy, okręgu.
Rysowanie okręgów z wykorzystaniem funkcji trygonometrycznych sin, cos jak w artykule jest bardzo nieefektywne i np. ATARI sobie z tym nie radził. Poniżej podaje szybki algorytm generowania elips i okręgów

Elipsa opisana jest następującymi równaniami parametrycznymi.
x(t)=a cos(t)+x0
y(t)=b sin(t)+y0

Po zróżniczkowaniu równań otrzymujemy.
dx/dt=-a sin(t)
dy/dt=b cos(t)

Po podstawieniu pierwszych do drugich mamy.
dx=-a/b(y(t)-y0)dt
dy=b/a(x(t)-x0)dt

Wiadomo, że:
x(t+dt)=x(t)+dx oraz; y(t+dt)=y(t)+dy

Po podstawieniu wyliczonych przyrostów do ostatnich równań dostajemy finalny wynik (!).
x(t+dt)=x(t)-a/b(y(t)-y0)dt
y(t+dt)=y(t)+b/a(x(t)-x0)dt

Powyższe równania możemy wykorzystać bezpośrednio do generowania punktów. Jeśli chcemy mieć okrąg z N=100 punktów wyliczamy przyrost parametru dt=2*pi/N, przyjmujemy warunki początkowe x(0)=a+x0, y(0)=y0 i liczymy kolejne punkty z finalnego wyniku (!).

Poniżej zamieszczam program wykorzystujący opisaną metodę. W programie wyznaczana jest większa liczba punktów N=100 niż jest zapamiętywana n=50 w celu zwiększenia dokładności. Punkty okręgu zapamiętywane są w tablicach x, y.

// Kolo.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <conio.h>
#include <stdio.h>
#include <math.h>

int _tmain(int argc, _TCHAR* argv[])
{
FILE *Plik;
double dt, a, b, xz, yz;
double X1, Y1;
double *x, *y;
int N, n, i, j, k;

//Parametry rysowanego okręgu
//a,b promienie elipsy, xz,yz-położenie srodka, jeśli a=b=r dostajemy okrąg
a=16; b=5; xz=2; yz=10;
//N-Liczba kroków algorytmu numerycznego (od niego zależy dokładność wyznaczanych punkrów okregu)
//n-Liczna zapemiętywanych punktow okręgu  N>n
N=100; n=50;

k=floor((double)N/(double)(n-1)+0.5);
n=floor((double)N/(double)(k)+0.5)+1;
x= new double[n];
y= new double[n];

dt=2*3.14/N;
X1=a; Y1=0;
x[0]=X1+xz;y[0]=Y1+yz;
a=a/b; b=1/a;
j=1;
for(i=1;i<=N;i++){
	X1=X1-a*Y1*dt;
	Y1=Y1+b*X1*dt;
if(i%k==0){
	x[j]=X1+xz;
	y[j]=Y1+yz;
	j++;
}
}
x[n-1]=x[0];
y[n-1]=y[0];

// Open for write 
if( (Plik = fopen("Elipsa.txt","w")) == NULL ){
  printf("The file 'Elipsa.txt' was not opened\n" );
  delete x;	delete y; return 1;	
}
else
  printf("The file 'Elipsa.txt' was opened\n" );
	
for(i=0;i<n;i++){
	fprintf(Plik,"%f %f\n", x[i],y[i]);
}

fclose(Plik);

delete x;
delete y;

return 0;

}

Prawdopodobnie podobna metoda wykorzystywana jest do obliczania FFT szybkiej transformaty Fouriera, w której kolejne punkty transformaty generowane są na podstawie poprzednich. Jeśli ktoś wie jak ona działa to niech się podzieli chętnie wysłucham.

Ja znalazlem gdzies tez dobry sposob na rysowanie okregu bez obliczen zmiennoprzecinkowych (dane wejsciowe to: xinit, yinit - srodek okregu; Radius - promien):

x:=0;
y:=Radius;
d:=3-2*Radius;
Repeat
putpixel(xinit+x,yinit+y,10);
putpixel(xinit+x,yinit-y,10);
putpixel(xinit-x,yinit+y,10);
putpixel(xinit-x,yinit-y,10);
putpixel(xinit+y,yinit+x,10);
putpixel(xinit+y,yinit-x,10);
putpixel(xinit-y,yinit+x,10);
putpixel(xinit-y,yinit-x,10);
if(d < 0) then
d := d + 4 *x +6
else
begin
d := d + 4 * ( x -y ) + 10;
y=y - 1;
end;
x = x + 1;
Until not (x <= y);

Cholera, jeblem sie ;) Dalem na linie bo to metoda tuz pod rysowaniem kolka w moim projekcie, ale moze tez sie przyda.
Daje okrag:

int xv, yv;
for (xv = -r; xv < r; xv++)
{
    yv = (int) round(sqrt((float) r * r - xv * xv));
    canvas.putPoint(x + xv, y + yv);
    canvas.putPoint(x + xv, y - yv);
    canvas.putPoint(x + yv, y + xv);
    canvas.putPoint(x - yv, y + xv);
};    

Dobrze jest załadować sin(alfa) i cos(alfa) do tablicy, wtedy mamy szybciej obliczane wartości, a jeśli ktoś chce szybko narysować okrąg to znacznie szybszy jest poniższy kod nie zawierający funkcji trygonometrycznych:
int x;
int y;
int d;
x = x1;
d = x1<x2?1:-1;
float g = (float) (y2 - y1) / (x2 - x1);
while (x1<x2?x<=x2:x>=x2) {
y = (int) round(y1 + g * (x - x1));
putPoint(x,y);
x += d;
};
y = y1;
g = (float) (x2 - x1) / (y2 - y1);
d = y1<y2?1:-1;
while (y1<y2?y<=y2:y>=y2) {
x = (int) round(x1 + g * (y - y1));
putPoint(x,y);
y += d;
};

Czyli rysuje na podstawie r:=sqrt(sqr(x)+sqr(y)).
Mając x (zaczynamy od x= r*-1, kończymy na x = r) obliczamy
y = sqrt(sqr(r)-sqr(x))
Oczywiscie narysujemy wtedy tylko od dolu lub gory, ale z pomoca przychodza operatory +/- i mozna w jednej petli narysowac kolo od wszystkich czterech stron ;)

Bardzo przydatne funkcje. Korzystam z nich bardzo często. A w połączeniu z assemblerowskim ryowaniem punktu [odpowiednik putpixel] to działa bardzo szybko. Teraz tylko jeszcze dodać obsługę myszki i można robić drugiego Photoshopa. :P Pozdrowienia.