Промена величине цртежа¶
Видели смо како можемо да организујемо цртање, да би цртање сложенијих цртежа на више места на екрану било једноставније. Слична је ситуација када желимо да цртеж буде мањи или већи, или да се нацрта на више места у различитим величинама.
Наравно, већ се подразумева да ћемо сва рачунања обављати у самом програму, јер смо свесни предности таквог начина рада. Када добро организујемо цртање, може бити довољно да се у програму промени један једини број па да добијемо цртеж друге величине.
Да бисмо научили како да правимо цртеже којима величина може лако да се мења, хајде прво да видимо шта све треба да се промени у наредбама за цртање да би се добио цртеж неке друге величине.
Подсетимо се, центар облака смо означили са \((x_c, y_c)\) и изабрали за сидро, а облак смо цртали помоћу ове функције:
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);
}
Рецимо да сада хоћемо да облак буде двоструко мањи, а да му средина и даље буде у тачки \((x_c, y_c)\). Ради тога је потребно смањити полупречнике сва три круга на пола претходне величине (25 пиксела уместо 50, а 15 уместо 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, 50, 50);
g.FillEllipse(cetka, xc - 80, yc - 30, 30, 30);
g.FillEllipse(cetka, xc + 20, yc - 30, 30, 30);
}
Да би цртеж и даље личио на облак, потребно је и да кругове међусобно приближимо. Прецизније речено, растојања центара мањих кругова од центра великог круга треба такође да буду два пута мања него пре, то јест по 25 уместо по 50 пиксела. Тиме добијамо ове наредбе:
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 - 25, yc - 25, 50, 50);
g.FillEllipse(cetka, xc - 40, yc - 15, 30, 30);
g.FillEllipse(cetka, xc + 10, yc - 15, 30, 30);
}
У општем случају не желимо увек двоструко мањи облак (или било који цртеж), него да облаци могу да буду различитих величина. Осим тога, не желимо да за сваку величину облака правимо посебну функцију, него да имамо једну функцију која може да црта облак задате величине. Најудобније би било да можемо величину облака да задајемо само једним бројем и да променом тог једног броја мењамо величину облака. Да бисмо то постигли, потребно је да све величине које се мењају са променом величине облака (а то су растојања између центара кругова и полупречници тих кругова) изразимо помоћу једне величине. Можемо на пример за ту једну величину да узмемо полупречник средњег круга, који ћемо поново да означимо са \(r\). Растојања центара мањих кругова до центра већег круга су једнака управо по \(r\), а полупречник мањих кругова је једнак \({3 \over 5} r\) без обзира на величину облака. Када искористимо све ове везе које смо уочили, функција за цртање облака добија следећи изглед:
private void Oblak(Graphics g, int xc, int yc, int r, int siva)
{
Color boja = Color.FromArgb(siva, siva, siva);
Brush cetka = new SolidBrush(boja);
// crtamo oblak od tri kruga
g.FillEllipse(cetka, xc - r, yc - r, 2 * r, 2 * r);
int rMalo = (int)Math.Round(3.0 * r / 5.0);
g.FillEllipse(cetka, xc - r - rMalo, yc - rMalo, 2 * rMalo, 2 * rMalo);
g.FillEllipse(cetka, xc + r - rMalo, yc - rMalo, 2 * rMalo, 2 * rMalo);
}
Функција има један параметар више, тако да осим положаја и нијансе сиве боје, овој функцији задајемо и величину облака. Сада сасвим лако можемо да правимо и овакве цртеже:
Довољно је да из функције Form1_Paint позивамо функцију Oblak са разним положајима, нијансама и величинама.
private void Form1_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
// crtamo sunce
g.FillEllipse(new SolidBrush(Color.Yellow), 20, 20, 160, 160);
Oblak(g, 240, 200, 40, 180);
Oblak(g, 270, 250, 50, 210);
Oblak(g, 230, 100, 50, 230);
Oblak(g, 80, 80, 30, 190);
Oblak(g, 110, 320, 60, 255);
Oblak(g, 280, 70, 20, 210);
Oblak(g, 310, 80, 15, 220);
Oblak(g, 330, 55, 15, 240);
}
Хајде да сада набројимо кораке за уопштени поступак, којим бисмо преправили цртање било ког помичног цртежа тако да му се може мењати и величина. Поступак ћемо испочетка спровести на наредбама за цртање помичног облака, од којих почињемо
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);
али овај пут без гледања у слику и без улажења у то шта која од ових наредби исцртава. На тај начин ћемо наредбе за цртање трансформисати онако како бисмо то урадили (и како ћемо касније радити) за било коју другу слику.
Први корак: треба да одредимо једну дужину на цртежу, која ће да се задаје директно, као параметар функције. Ову изабрану дужину можемо да назовемо основна дужина или јединица мере.
У примеру облака, основна дужина је полупречник средњег круга, коју означавамо са r. Тренутна вредност основне величине је 50.
Други корак: за сваки правоугаоник или елипсу који се појављују на цртежу, ширину и висину тог правоугаоника или елипсе треба да изразимо сразмерно основној дужини. То значи да, ако је наша основна дужина означена са \(r\), све остале дужине у програму (ширине и висине правоугаоника и елипси) ће бити представљене као неки број помножен са \(r\), на пример \(2r\) или \(5r\). Овај број одређујемо из односа тражене дужине и изабране основне дужине на почетном цртежу, односно наредбама које генеришу почетни цртеж (тај однос остаје исти и када се величина цртежа буде мењала).
У примеру са облаком, основна дужина је 50, а појављују се дужине 100 и 60, које треба изразити као неки умножак основне дужине r. После овог корака, наше наредбе постају
g.FillEllipse(cetka, xc - 50, yc - 50, 2 * r, 2 * r); g.FillEllipse(cetka, xc - 80, yc - 30, 1.2 * r, 1.2 * r); g.FillEllipse(cetka, xc + 20, yc - 30, 1.2 * r, 1.2 * r);
приметимо само ради јасноће (без утицаја на општи поступак) да је \(1.2 \cdot r\) исто што и \(2 \cdot {3 \over 5} \cdot r = 2 \cdot r_1\).
Трећи корак: X координате свих битних тачака одређујемо у односу на главну тачку, тако што \(x\) координати главне тачке додајемо или одузимамо одређени број основних дужина. Потребан број основних дужина се може одредити из односа на почетном цртежу, односно из односа бројева у почетном облику наредби за цртање.
У случају облака, помаке 50, 80 и 20 по \(x\) координати треба изразити као умношке основне дужине. Тако добијамо наредбе
g.FillEllipse(cetka, xc - r, yc - 50, 2 * r, 2 * r); g.FillEllipse(cetka, xc - 1.6 * r, yc - 30, 1.2 * r, 1.2 * r); g.FillEllipse(cetka, xc + 0.4 * r, yc - 30, 1.2 * r, 1.2 * r);
Поново само ради јасноће, примећујемо да је \(x_c - 1.6 \cdot r = x_c - r - r_1\), као и да је \(x_c + 0.4 \cdot r = x_c + r - r_1\).
Четврти корак: поступак из трећег корака примењујемо и на \(y\) координате: сваку \(y\) координату изражавамо као „Y координата главне тачке плус одговарајући број основних дужина”, где тај одговарајући број може да буде и нула или негативан.
У случају облака, помаке -50, и -30 по \(y\) координати изражавамо као умношке основне дужине и добијамо
g.FillEllipse(cetka, xc - r, yc - r, 2 * r, 2 * r); g.FillEllipse(cetka, xc - 1.6 * r, yc - 0.6 * r, 1.2 * r, 1.2 * r); g.FillEllipse(cetka, xc + 0.4 * r, yc - 0.6 * r, 1.2 * r, 1.2 * r);
Наравно, ако смо претходно увели rMalo као три петине од \(r\), онда је израз yc - 0.6 * r равноправан са yc - rMalo.
Да бисмо боље разумели поступак промене величине цртежа, применићемо га и на примеру медведића.
Пример - величина меде
Ово су функције помоћу којих можемо да нацртамо медведића на било ком месту на екрану.
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
}
Да бисмо могли да мењамо величину цртежа, уведимо основну дужину, на пример \(a = 5\). Сада све полупречнике можемо да изразимо помоћу \(a\) овако:
private void CrtajMedu(Graphics g, int cx, int cy)
{
UokvirenKrug(g, Color.Yellow, cx - 60, cy - 70, 9 * a); // levo uvo
UokvirenKrug(g, Color.Yellow, cx + 60, cy - 70, 9 * a); // desno uvo
UokvirenKrug(g, Color.Yellow, cx, cy, 20 * a); // glava
UokvirenKrug(g, Color.Yellow, cx, cy + 50, 10 * a); // njuska
UokvirenKrug(g, Color.Black, cx - 50, cy - 30, 3 * a); // levo oko
UokvirenKrug(g, Color.Black, cx + 50, cy - 30, 3 * a); // desno oko
UokvirenKrug(g, Color.Black, cx, cy + 20, 3 * a); // vrh njuske
}
Као основна дужина се може изабрати било који број. Ми смо за основну дужину изабрали 5 пиксела зато што су тако сви полупречници целобројни умношци од \(a\) и можемо лако да их израчунамо напамет. На пример, полупречник од 45 пикесла изражавамо као \(45=9 \cdot 5 = 9 \cdot a\), и тако даље.
Сада је потребно да координате центара свих осталих кругова изразимо полазећи од главне тачке \((cx, cy)\), померајући се за потребан број дужина \(a\) у смеру \(x\) и \(y\) осе. Узмимо као пример десно уво медведића.
\(x\) координата центра десног увета је \(cx + 60 = cx + 12 a\), док је \(y\) координата \(cy - 70 = cy - 14 a\). Када ово урадимо за све центре кругова, долазимо до следећег облика програма:
private void CrtajMedu(Graphics g, int cx, int cy)
{
UokvirenKrug(g, Color.Yellow, cx - 12 * a, cy - 14 * a, 9 * a); // levo uvo
UokvirenKrug(g, Color.Yellow, cx + 12 * a, cy - 14 * a, 9 * a); // desno uvo
UokvirenKrug(g, Color.Yellow, cx, cy, 20 * a); // glava
UokvirenKrug(g, Color.Yellow, cx, cy + 10 * a, 10 * a); // njuska
UokvirenKrug(g, Color.Black, cx - 10 * a, cy - 6 * a, 3 * a); // levo oko
UokvirenKrug(g, Color.Black, cx + 10 * a, cy - 6 * a, 3 * a); // desno oko
UokvirenKrug(g, Color.Black, cx, cy + 4 * a, 3 * a); // vrh njuske
}
Сада можемо не само да премештамо или копирамо медведића по екрану, него и да га приказујемо у разним величинама. Као потврду да мењање величине заиста ради, можете позив функције
CrtajMedu(g, ClientSize.Width / 2, ClientSize.Height / 2, 6);
која црта медведића са главном тачком у центру прозора, да замените са следећих пет:
CrtajMedu(g, 85, 100, 4);
CrtajMedu(g, 235, 100, 3);
CrtajMedu(g, 50, 250, 2);
CrtajMedu(g, 150, 250, 2);
CrtajMedu(g, 250, 250, 2);
Ископирајте или препишите ових пет линија кода у програм и испробајте! Размислите како би изгледао програм који приказује ових пет медведића без функције CrtajMedu и израчунавања координата у програму.
Задатак за вежбу - величина кућице
Покушајте да измените и програм из претходне лекције који црта 4 кућице, тако да се лако може мењати и величина нацртаних кућица. Након измене функције kuca, испробајте разне распореде, боје и величине кућа, на пример овај испод, или неки који сами изаберете:
Kuca(g, 150, 90, 8, Color.FromArgb(220, 220, 220));
Kuca(g, 250, 130, 9, Color.White);
Kuca(g, 350, 160, 10, Color.FromArgb(255, 255, 150));
Kuca(g, 50, 150, 10, Color.Khaki);