PDA

Zobacz pełną wersję : Proszę o pomoc - błąd w programie turbo pascal



margoo
15-03-07, 07:03
Witam wszystkich!:grin:
Mam problem z programem w turbo pascalu.
Napisaam program, ktry mia wywietla lini piksel po pikselu. W sumie dziaa, ale nie logicznie. Wywietla tylko linie pionowe albo poziome ewentualnie linie pod ktem 45 %. A mia wywietla pod kadym ktem. Nie mam pojcia, co moe by nie tak.:?: Dlatego te zwracam si do Was o pomoc. Przesyam kod programu. Do edycji i kompilacji uywam FreePascal'a 2.0.0.
A oto kod programu:

Program Odcinek;
uses Graph;
var
Driver,Mode,i,n,n1,n2,x,y,x1,y1,x2,y2: integer;
dx,dy: real;
Pixel: word;

begin

{ tryb graficzny }

Driver:=Detect;
InitGraph(Driver,Mode,'');

{ wprowadzanie danych przez u-ytkownika }

writeln('Podaj wspolrzedne punktu P1');
writeln('Podaj x1= ');
readln(x1);
writeln('Podaj y1= ');
readln(y1);
writeln('Podaj wspolrzedne punktu P2');
writeln('Podaj x2= ');
readln(x2);
writeln('Podaj y2= ');
readln(y2);

{ obliczenia }
{ liczba krokow }

n1:=(abs(x2-x1));
n2:=(abs(y2-y1));
if n1<n2 then
n:=n2;
if n1>n2 then
n:=n1;
if n1=n2 then
n:=n1;
dx:=(x2-x1)/n; {dlugosc kroku w kierunku osi X}
dy:=(y2-y1)/n; {dlugosc kroku w kierunku osi Y}

{ punkt poczatkowy }

x:=x1;
y:=y1;

{ petla }

for i:=1 to n do
begin

{ rysuj punkt(x,y) }

PutPixel(x,y,14);
x:=round(x+dx);
y:=round(y+dy);
end;
readln
end.

Prosz o sprawdzenie i podpowied, co moe by nie tak.
Z gry dzikuje.
Pozdrawiam.

RRybak
15-03-07, 11:40
Chwilowo nie mam pod ręką kompilatora pascala, żeby to sprawdzić, ale nasuwa mi się jedno spostrzeżenie.

x=300-100
y=150-100 --> ilość kroków = 200 (bierzesz większą wartość)

Następnie dzielisz obliczając długość kroku:
dx = 1;
dy = 0.25;

W pętli rysowania dodajesz do całkowitej liczby wartość ułamkową (150 + 0.25) i zaokrąglasz ją do integera -> wynik = 150. Ile razy byś operacji tą metodą nie wykonała będzie wisiało na 150 i nie ruszy się przez zaokrąglanie. Stąd rysowanie w tylko jedną stronę.

Możliwe rozwiązania:
- zamień kierunki IF w obliczaniu ilości kroków (ale wtedy linia będzie poszarpana)
- wyliczaj kolejny punkt mnożąc (punkt_start + (nr_kroku * długość_kroku)). To oczywiście zaokrąglone. W tym przypadku prawdopodobnie nr_kroku będzie trzeba podawać jako n-1, żeby zaczynać od 0 i nie przejechać za punkt końcowy.

margoo
16-03-07, 17:10
Dzięki za odpowiedź.
Ale mam prośbę. Czy można by mi to wytłumaczyć bardziej łopatologicznie?

- zamień kierunki IF w obliczaniu ilości kroków (ale wtedy linia będzie poszarpana)
- wyliczaj kolejny punkt mnożąc (punkt_start + (nr_kroku * długość_kroku)). To oczywiście zaokrąglone. W tym przypadku prawdopodobnie nr_kroku będzie trzeba podawać jako n-1, żeby zaczynać od 0 i nie przejechać za punkt końcowy.
Jestem jeszcze osobą początkującą w temacie pascala :oops: .
Pozdrawiam.

RRybak
16-03-07, 17:48
Okej. A więc przez "odwrócenie" miałem na myśli odwrócenie nierówności. Teraz jak się tak głębiej nad tym zastanowię, to na podstawie znalezionego błędu możemy uzyskać równie głupi efekt w postaci linii wyjeżdżających poza ekran. Tak więc:

if n1>n2 then n:=n2;
if n1 < n2 then n:=n1; powyższy kod zapewni nam większą precyzję obliczeń (większa liczba kroków, a nie "maksymalna" jak przyjęłaś na początku), ale niestety ma/może mieć swoje skutki uboczne - więc to pozostawiam do eksperymentów PO wprowadzeniu poprawki obliczania aktualnych pozycji. Końcową pętlę spróbuj zamienić na:

for i:=0 to n-1 do
begin

{ rysuj punkt(x,y) }
x:=round(x1 + (i*dx));
y:=round(y1 + (i*dy));
PutPixel(x,y,14);

end;

Jaki efekt? Aktualny piksel jest wyliczany na bieżąco w trakcie pętli. Wcześniej dodawałaś do liczb naturalnych (bez części ułamkowej) liczby rzeczywiste (ułamki). Wynikiem tego była jakaś liczba rzeczywista (np. 150.25), którą funkcją Round() okrajałaś z powrotem do wartości naturalnej (bez ułamka = 150). Rozumowanie poprawne, bo współrzędne pikseli są wartościami naturalnymi, jednak jak widzisz w tym przypadku - błędne było założenie, przez co traciłaś informację o części ułamkowej i dlatego linia nie zmieniała swojej np. pozycji y (rysowanie tylko w poziomie). Bla bla - rozpisałem się.

Powyższa poprawka nie przechowuje wartości bieżącego piksela, tylko wylicza go na żywo, czyli nijako te twoje (x=x+dx) zostało włożone w mnożenie "na bieżąco" - zwykłe zwijanie i rozwijanie wyrażeń matematycznych. Dzięki temu nie musimy się nigdzie martwić o to czy zapamiętaliśmy część ułamkową, czy nie - wynik mamy taki jak chcemy i dopiero jego zaokrąglamy do liczby naturalnej. Czyli jest OK.

Zakładam, że może to być trochę niezrozumiałe, więc na wszelki wypadek podaję przykład jak TWOJĄ metodą ominąć ten problem.
Rozszerzamy deklarację zmiennych:

var
Driver,Mode,i,n,n1,n2,x,y,x1,y1,x2,y2: integer;
dx,dy: real;
Pixel: word;
ostatni_x, ostatni_y : real;

i teraz pętla na dole:


{ punkt poczatkowy }

x:=x1;
y:=y1;
ostatni_x=x1;
ostatni_y=y1;

{ petla }

for i:=1 to n do
begin

{ rysuj punkt(x,y) }
x = Round(ostatni_x); {zaokrąglamy dopiero uzyskany wynik}
y = Round(ostatni_y);
PutPixel(x,y,14);
ostatni_x:=ostatni_x+dx; {a tutaj wynik mamy kolejno 150.25, 150.50, 150. 75 - a nie 150.. 150... 150...}
ostatni_y:=ostatni_y+dy; {czyli pamiętamy wartości ułamkowe - Ty je gubiłaś ODCINAJĄC przez Round() )

end;

Trochę się rozpisałem, ale MAM NADZIEJĘ, że przynajmniej odrobinę udało mi sie wyjaśnić :)

margoo
18-03-07, 17:19
Dzięki wielkie.
Właśnie o to mi chodziło:-D . Wykorzystałam tylko tą część:


for i:=0 to n-1 do
begin

{ rysuj punkt(x,y) }
x:=round(x1 + (i*dx));
y:=round(y1 + (i*dy));
PutPixel(x,y,14);

end;
Przy użyciu odwrócenia


if n1>n2 then n:=n2;
if n1 < n2 then n:=n1;
linia robiła się przerywana. Więc zostawiłam tak jak było. Co prawda linia jest teraz "schodkowa", ale to nie przeszkadza.
Jeszcze raz wielkie dzięki!
Pozdrawiam.

Alpha
19-03-07, 00:05
Przy użyciu odwrócenia


if n1>n2 then n:=n2;
if n1 < n2 then n:=n1;
linia robiła się przerywana. Więc zostawiłam tak jak było. Co prawda linia jest teraz "schodkowa", ale to nie przeszkadza.

W przypadku kreślenia linii prostej na ekranie rastrowym (siatce prostokątnej pixeli) linia zawsze będzie albo przerywana albo "schodkowa". Jak za n przyjmiesz mniejszą z n1,n2 - pixeli (punktów) będzie mniej, i gdy linia będzie stroma, będzie wyglądać jak przerywana; jeśli przyjmiesz odwrotnie - będzie schodkowa, bo pixeli narysowanych będzie więcej.
Tak ściśle mówiąc to ta "schodkowość" nazywa się formalnie aliasing, a unika się tego brzydkiego efektu za pomocą (jakże by inaczej :-) ) antyaliasingu, czyli rozmywania koloru, ale to jest trudniejsze.
Zwróć jednak, Rybaku, uwagę, że właściwie zapomniałeś rysować ostatniego punktu (x2,y2). Co prawda go sobie obliczysz, ale narysowanie powinno znaleźć się po pętli.
Margo, tak w ogóle to polecam algorytm Bresenhama http://pl.wikipedia.org/wiki/Algorytmy_Bresenhama.
Nie jest tak elementarnie prosty jak ten, ale daje tyle pixeli, ile potrzeba, a działa na liczbach całkowitych, co w jego przypadku akurat nie powoduje zniekształceń, które były powodem Twoich kłopotów.

margoo
24-03-07, 21:55
Alpha. Dzikuj za link. Na pewno mi si przyda. Ale jak ju wczeniej pisaam nie przeszkadza mi to, e linia jest "aliasing'owa". Program ma pokazywa na jakiej zasadzie dziaa rysowanie odcinka punkt po punkcie, czyli w tym wypadku nawet jest wskazane, eby takie niedocignicia byy widoczne.
Jeszcze raz dzikuje Wam za pomoc. Pozdrawiam.:-D