Померање цртежа¶
До сада смо видели како можемо да стварамо цртеже састављене од основних облика. При томе је за сваки од тих облика било потребно одредити тачан положај да би се сви делови уклопили у целину. За неке цртеже је било потребно да се координате појединих тачака израчунају на основу познатих координата других тачака или неких величина на слици.
Видели смо да када рачунање координата уврстимо у сам програм који и црта слику, такав програм постаје удобнији за експериментисање са сликом и погоднији за преправљање и генерисање нових, сличних слика.
Још један разлог да уврстимо рачунање координата директно у програм је што нам то, уз мало датне организације омогућава да исту или врло сличну слику врло једноставно нацртамо више пута на екрану. Пре него што дођемо до детаља овог поступка, било би добро да проверите потребно предзнање и одговорите на ова питања:
- (50, 60)
- Покушајте поново
- (50, 80)
- Покушајте поново
- (40, 70)
- Тачно
- (60, 70)
- Покушајте поново
- (40, 60)
- Покушајте поново
Q-14: Kоје су координате тачке која се налази 10 пиксела лево од тачке (50, 70)?
- (50, 60)
- Покушајте поново
- (50, 80)
- Тачно
- (40, 70)
- Покушајте поново
- (60, 70)
- Покушајте поново
- (40, 60)
- Покушајте поново
Q-15: Kоје су координате тачке која се налази 10 пиксела испод тачке (50, 70)?
- g.DrawRectangle(olovka, 70, 120, 50, 60))
- Покушајте поново
- g.DrawRectangle(olovka, 100, 150, 110, 120))
- Покушајте поново
- g.DrawRectangle(olovka, 100, 150, 50, 60))
- Покушајте поново
- g.DrawRectangle(olovka, 70, 120, 80, 90))
- Тачно
- g.DrawRectangle(olovka, 70, 180, 80, 90))
- Покушајте поново
Q-16: Правоугаоник је нацртан помоћу g.DrawRectangle(olovka, 100, 150, 80, 90)
. Како се може нацртати правоугаоник исте величине који се налази 30 пиксела лево и 30 пиксела изнад овог правоугаоника?
- g.FillEllipse(cetka, x, y-a, a, a);
- Покушајте поново
- g.FillEllipse(cetka, x, y-2*a, a, a);
- Покушајте поново
- g.FillEllipse(cetka, x, y+a, a, a);
- Тачно
- g.FillEllipse(cetka, x, y+2*a, a, a);
- Покушајте поново
Q-17: Круг је нацртан помоћу наредбе g.FillEllipse(cetka, x, y, a, a);
Како се може нацртати круг исте боје и величине, који додирује дати круг одоздо?
Преправљање непомичног цртежа у помични¶
Овакву слику можемо да нацртамо на пример помоћу следеће функције.
private void Form1_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
Brush zutaCetka = new SolidBrush(Color.Yellow);
Brush belaCetka = new SolidBrush(Color.White);
g.FillEllipse(zutaCetka, 20, 50, 160, 160); // crtamo sunce
// crtamo oblak od tri kruga
g.FillEllipse(belaCetka, 150, 150, 100, 100);
g.FillEllipse(belaCetka, 120, 170, 60, 60);
g.FillEllipse(belaCetka, 220, 170, 60, 60);
}
Облак смо представили помоћу три круга, једног већег у средини и два мања око њега:
g.FillEllipse(belaCetka, 150, 150, 100, 100);
g.FillEllipse(belaCetka, 120, 170, 60, 60);
g.FillEllipse(belaCetka, 220, 170, 60, 60);
Шта треба да урадимо да бисмо исти такав облак нацртали на другом месту, на пример 30 пиксела изнад и 80 пиксела десно? Ако сте тачно одговорили на претходна питања, неће вам бити превише тешко да закључите да се облак померен на тражени начин може добити помоћу следеће три наредбе:
g.FillEllipse(belaCetka, 230, 120, 100, 100);
g.FillEllipse(belaCetka, 200, 140, 60, 60);
g.FillEllipse(belaCetka, 300, 140, 60, 60);
Параметре ових наредби смо, наравно, добили додавањем 80 на све \(x\) координате и одузимањем 30 од свих \(y\) координата. Овај начин померања цртежа је прихватљив ако хоћемо да нацртамо само један нови облак и већ имамо коначну одлуку о томе где ћемо тај облак да нацртамо. Међутим, када хоћемо да нацртамо неколико облака и да при томе експериментишемо са њиховим распоредом, овај начин померања није најудобнији. Како да прилагодимо цртање облака, да би испробавање разних распореда облака било што једноставније?
Један удобан начин је да напишемо функцију која црта један облак. При томе желимо да функцији прослеђујемо што мање координата да бисмо имали што мање посла при позивању функције и да би нам експериментисање било што удобније. Ако желимо да облак слободно померамо, функцији треба проследити најмање две координате, које ће на неки начин да задају положај облака. Рецимо да смо одлучили да функцији прослеђујемо координате центра облака \((x_c, y_c)\), око којег тај облак треба да буде нацртан (види слику).
Са слике видимо и да је
\(x = x_c - r\),
\(x_1 = x_c - r - r_1\),
\(x_2 = x_c + r - r_1\).
Када уврстимо \(r = 50\) и \(r_1 = 30\), добијамо
\(x = x_c - 50\),
\(x_1 = x_c - 80\),
\(x_2 = x_c + 20\).
Слично томе, за \(y\) координате имамо
\(y = y_c - r\),
\(y_1 = y_2 = y_c - r_1\),
а после уврштавања \(r = 50\) и \(r_1 = 30\) добијамо
\(y = y_c - 50\),
\(y_1 = y_2 = y_c - 30\)
Функцију за цртање облака сада можемо да напишемо овако:
private void Oblak(Graphics g, int xc, int yc, int siva)
{
Color boja = Color.FromArgb(siva, siva, siva);
Brush cetka = new SolidBrush(boja);
// crtamo oblak od tri kruga
g.FillEllipse(cetka, xc - 50, yc - 50, 100, 100);
g.FillEllipse(cetka, xc - 80, yc - 30, 60, 60);
g.FillEllipse(cetka, xc + 20, yc - 30, 60, 60);
}
Овде смо успут омогућили и задавање боје, то јест сиве нијансе облака.
Писање ове функције је изискивало нешто труда, али када је једном напишемо, експериментисање са распоредом (и нијансама) облака постаје веома једноставно - довољно је из функције Form1_Paint позивати функцију Oblak, задајући центре и боје тих облака по жељи:
private void Form1_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
Brush zutaCetka = new SolidBrush(Color.Yellow);
g.FillEllipse(zutaCetka, 20, 50, 160, 160); // crtamo sunce
Oblak(g, 240, 200, 180);
Oblak(g, 270, 250, 210);
Oblak(g, 230, 100, 230);
Oblak(g, 80, 80, 190);
Oblak(g, 110, 320, 255);
}
Размислите како би изгледао програм који црта исту ову слику без функције Oblak. Број наредби би се брзо умножавао, што би програм чинило мање прегледним. Осим тога, било би пртребно више преправљања за сваку нову слику, па би рад био мање удобан. Због свега тога и могућност грешке би била већа, а шансе да искуство програмирања буде пријатно би биле мање.
Резимирајмо, уз мала уопштења, шта је потребно да се уради да бисмо могли да прказујемо један цртеж на разним местима:
Треба да изаберемо једну тачку на цртежу чије се координате задају директно. Ову изабрану тачку зваћемо главна тачка, (понекад се ова тачка назива и сидро, енгл. anchor). У примеру облака, главна тачка је центар средњег круга.
Након избора главне тачке, координате свих осталих битних тачака одређујемо у односу на њу, тако што на координате главне тачке додајемо или одузимамо одређени померај.
У општем случају, на цртежу може бити и других облика осим кругова. Тачке које одређују положаје тих облика су:
за дуж: њени крајеви
за многоугао: његова темена
за правоугаоник: његово горње лево теме
за елипсу: горње лево теме правоугаоника описаног око те елипсе
за круг: горње лево теме павоугаоника (квадрата) описаног око тог круга
Све ове тачке треба задати у односу на главну тачку, то јест изразити њихове координате као координате главне тачке увећане или умањене за неку вредност. При померању цртежа (за сада) не мењамо његову величину, па зато нећемо мењати ширину и висину правоугаоника, елипсе и круга.
Проверите колико сте разумели претходна објашњења и одгоровите на питања.
- g.FillEllipse(cetka, x, y, 100, 60);
- Покушајте поново
- g.FillEllipse(cetka, x+120, y+90, 100, 60);
- Покушајте поново
- g.FillEllipse(cetka, x+20, y-10, 100, 60);
- Тачно
- g.FillEllipse(cetka, x-20, y+10, 100, 60);
- Покушајте поново
Q-18: Желимо да прилагодимо цртеж који се састоји од неколико основних облика, тако да се све црта у односу на сидро са координатама x=100, y=100. Једна од наредби које формирају цртеж је
g.FillEllipse(cetka, 120, 90, 100, 60);
Која наредба треба да замени дату?
- g.DrawLine(olovka, x-50, y-50, 150, 150);
- Покушајте поново
- g.DrawLine(olovka, x-50, y-50, x+50, y+50);
- Тачно
- g.DrawLine(olovka, x-50, x+50, y-50, y+50);
- Покушајте поново
- g.DrawLine(olovka, x+50, y+50, x+150, y+150);
- Покушајте поново
Q-19: Желимо да прилагодимо цртеж који се састоји од неколико osnovnih облика, тако да се све црта у односу на сидро са координатама x=100, y=100. Једна од наредби које формирају цртеж је
g.DrawLine(olovka, 50, 50, 150, 150);
Која наредба треба да замени дату?
- g.FillRectangle(cetka, x-50, y-50, x, y);
- Покушајте поново
- g.FillRectangle(cetka, x, y, 100, 100);
- Покушајте поново
- g.FillRectangle(cetka, x+50, y+50, x+100, y+100);
- Покушајте поново
- g.FillRectangle(cetka, x-50, y-50, 100, 100);
- Тачно
Q-20: Желимо да прилагодимо цртеж који се састоји од неколико облика, тако да се све црта у односу на сидро са координатама x=100, y=100. Једна од наредби које формирају цртеж је
g.FillRectangle(cetka, 50, 50, 100, 100);
Која наредба треба да замени дату?
- Уместо g.DrawLine(olovka, a, b, c, d); позваћемо g.DrawLine(olovka, a+100, b, c, d);
- Покушајте поново
- Уместо g.FillEllipse(cetka, a, b, c, d); позваћемо g.FillEllipse(cetka, a-100, b-100, c, d);
- Покушајте поново
- Уместо g.FillRectangle(cetka, a, b, c, d); позваћемо g.FillRectangle(cetka, a+100, b, c+100, d);
- Покушајте поново
- Уместо g.FillRectangle(cetka, a, b, c, d); позваћемо g.FillRectangle(cetka, a+100, b, c, d);
- Тачно
- Уместо g.FillRectangle(cetka, a, b, c, d); позваћемо g.FillRectangle(cetka, a-100, b, c, d);
- Покушајте поново
Q-21: Желимо да померимо цртеж који се састоји од неколико облика надесно за 100 пиксела. Означите тачна тврђења.
Следи још један пример претварања фиксног цртежа у помични.
Пример - положај меде
Дат је следећи код, који исцртава главу медведића играчке:
private void UokvirenKrug(Graphics g, Color boja, int cx, int cy, int r)
{
g.FillEllipse(new SolidBrush(boja), cx - r, cy - r, 2 * r, 2 * r);
g.DrawEllipse(new Pen(Color.Black), cx - r, cy - r, 2 * r, 2 * r);
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
UokvirenKrug(g, Color.Yellow, 190, 80, 45); // levo uvo
UokvirenKrug(g, Color.Yellow, 310, 80, 45); // desno uvo
UokvirenKrug(g, Color.Yellow, 250, 150, 100); // glava
UokvirenKrug(g, Color.Yellow, 250, 200, 50); // njuska
UokvirenKrug(g, Color.Black, 200, 120, 15); // levo oko
UokvirenKrug(g, Color.Black, 300, 120, 15); // desno oko
UokvirenKrug(g, Color.Black, 250, 170, 15); // vrh njuske
}
У програму се седам пута позива функција UokvirenKrug, која задати круг уоквирује црном бојом (мада је за три мала црна круга могла да буде позвана и само функција FillEllipse). Да бисмо могли да мењамо положај цртежа, изаберимо главну тачку (сидро). Нека то буде центар великог круга, то јест главе медведића. Координате ове тачке су (250, 150). Сада је потребно да координате центара свих осталих кругова изразимо полазећи од главне тачке, померајући се за потребан број пиксела у смеру \(x\) и \(y\) осе. Узмимо као пример десно уво медведића.
\(x\) координата центра десног увета је \(310 = 250 + 60\), док је \(y\) координата \(80 = 150 - 70\). Одавде се види да координате центра десног увета можемо у програму да напишемо као (cx + 60, cy - 70)
, где су (cx, cy)
координате главне тачке. Спровођењем истог поступка за остале кругове добијамо функцију CrtajMedu:
private void UokvirenKrug(Graphics g, Color boja, int cx, int cy, int r)
{
g.FillEllipse(new SolidBrush(boja), cx - r, cy - r, 2 * r, 2 * r);
g.DrawEllipse(new Pen(Color.Black), cx - r, cy - r, 2 * r, 2 * r);
}
private void CrtajMedu(Graphics g, int cx, int cy)
{
UokvirenKrug(g, Color.Yellow, cx - 60, cy - 70, 45); // levo uvo
UokvirenKrug(g, Color.Yellow, cx + 60, cy - 70, 45); // desno uvo
UokvirenKrug(g, Color.Yellow, cx, cy, 100); // glava
UokvirenKrug(g, Color.Yellow, cx, cy + 50, 50); // njuska
UokvirenKrug(g, Color.Black, cx - 50, cy - 30, 15); // levo oko
UokvirenKrug(g, Color.Black, cx + 50, cy - 30, 15); // desno oko
UokvirenKrug(g, Color.Black, cx, cy + 20, 15); // vrh njuske
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
CrtajMedu(e.Graphics, ClientSize.Width / 2, ClientSize.Height / 2);
}
Овако написан програм нам омогућава да једноставно приказујемо медведиће на разним местима на екрану. На пример, можемо да уместо позива функције
CrtajMedu(e.Graphics, ClientSize.Width/2, ClientSize.Height / 2);
која црта медведића са главном тачком у центру прозора (као што је и био), употребимо следећа два позива:
CrtajMedu(e.Graphics, ClientSize.Width/2 - 120, ClientSize.Height / 2);
CrtajMedu(e.Graphics, ClientSize.Width/2 + 120, ClientSize.Height / 2);
Испробајте ово! Било би знатно теже нацртати другог медеведића да нисмо почетни програм прилагодили за овакву употребу.
Задаци за вежбу¶
Ако сте при вежбању израчунавања координата писали програме који цртају саобраћајне знаке, можете сада те програме да искористите и да вежбате понављање једног цртежа на више места.
У наставку је задатак са кодом који црта непомичну слику, а програм треба преправити тако да се слика лако помера и црта на више места.
Положај куће
Рецимо да сте написали ову функцију за цртање, а циљ вам је да преправите програм тако да кућица може једноставно да се премешта:
private void Form1_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
Brush braonCetka = new SolidBrush(Color.Brown);
Brush kakiCetka = new SolidBrush(Color.Khaki);
Brush crvenaCetka = new SolidBrush(Color.Red);
Point[] krov = new Point[] { new Point(50, 150), new Point(120, 100), new Point(190, 150) };
g.FillPolygon(crvenaCetka, krov); // krov
g.FillRectangle(kakiCetka, 50, 150, 140, 100); // kuca
g.FillRectangle(braonCetka, 60, 170, 30, 30); // levi prozor
g.FillRectangle(braonCetka, 150, 170, 30, 30); // desni prozor
g.FillRectangle(braonCetka, 100, 180, 40, 70); // vrata
}
Нека је главна тачка (x, y) = (50, 150)
. Напишите функцију private void Kuca(Graphics g, int x, int y, Color bojaZidova), у којој се црта (помична) кућа. Када се уверите да цртежи у два програма изгледају исто, испред позива Kuca(g, 50, 150, Color.Khaki);
додајте још 3 нова позива (и прилагодите величину формулара) да бисте добили слику попут оне испод.
private void Form1_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
Kuca(g, 150, 90, Color.FromArgb(220, 220, 220));
Kuca(g, 220, 130, Color.White);
Kuca(g, 350, 160, Color.FromArgb(255, 255, 150));
Kuca(g, 50, 150, Color.Khaki);
}