Elemente de grafică

Java API ofera programatorilor posibilități ample privind realizarea de desene, text stilizat și alte construcții grafice. Ne vom rezuma aici numai la câteva noțiuni elementare, utile în realizarea unor aplicații grafice simple.
 
Pentru aprofundare recomandăm lectia Working with Graphics din Java Tutorial. Studiul poate fi apoi continuat cu secțiunea 2D Graphics a aceluiași tutorial.

In principiu, desenarea se poate face pe orice componentă grafică. Există însă clasa java.awt.Canvas, ale cărei instanțe sunt simple panouri destinate desenării (în engleză Canvas este pânza pictorului). Pentru desenare se mai folosesc frecvent și clasele JPanel și JLabel.

Sistemul de coordonate folosit pentru grafică are originea în colțul din stânga sus al componentei, axa Ox este orientată catre dreapta, iar axa Oy este orientată în jos. Coordonatele se exprimă în numere întregi (int), având ca unitate de măsură pixelul (punctul de pe ecran).

Contextul grafic

Modul de realizare a imaginilor și desenelor este strâns dependent atât de echipament (hardware) cât și de sistemul de operare. Pentru a se asigura independența de platformă, în Java API a fost introdusă clasa abstractă java.awt.Graphics. Aceasta este clasa de bază a tuturor contextelor grafice, care permit trasarea de desene pe suprafața componentelor grafice realizate pe diverse dispozitive fizice. Pe fiecare platformă, în mediul de execuție Java, trebuie să existe o implementare a contextului grafic, adică o extindere a clasei Graphics, care conține toate câmpurile și metodele  acestei clase, dar este specifică platformei respective.
 
Un obiect din clasa Graphics încapsulează informația de stare a contextului grafic la care se referă și anume: 
    - referința la obiectul din clasa Component (sau dintr-o subclasa a acesteia) pe care se desenează; 
    - o translație a originii sistemului de coordonate; toate coordonatele din desen sunt raportate la această origine; 
    - decupajul curent (dreptunghiul în interiorul căruia se trasează desenul); 
    - culoarea curentă; 
    - fontul curent; 
    - operația logică pe pixeli curentă (XOR sau paint); 
    - alternarea curentă de culori pentru operația pe pixeli XOR. 

Originea sistemului de axe (punctul de coordonate 0,0) se găsește în colțul din stânga-sus al dreptunghiului de desenare. Axa 0x este îndreptată spre dreapta, iar axa 0y - in jos. 

Practic, clasa abstractă Graphics conține acele metode, care trebuie să existe în orice context grafic. Conținutul concret al acestor metode, deci modul efectiv în care se realizează funcțiile respective, depinde de contextul grafic real, deci de dispozitivul fizic pe care se face desenarea și de sistemul de operare folosit. Pe programatorul de aplicații sau miniaplicații în Java nu îl interesează însă acest lucru, deoarece el folosește în programele sale metodele clasei abstracte Graphics, fără să se preocupe de modul în care acestea vor fi executate. 


 
Principalele metode ale clasei Graphics sunt următoarele: 

    public abstract Graphics create() - creează și întoarce un nou obiect din clasa Graphics, care este o copie a obiectului pentru care se aplică această metodă; 
    public abstract Graphics create(int x, int y, int width, int height) - creează și întoarce o copie a obiectului din clasa Graphics căruia i se aplică, însă cu o noua translație a originii (x,y) și cu valori noi valori ale lățimii și înălțimii dreptungiului de desenare (suprafeței de decupare); 
    public abstract void translate(int x, int y) - translatează originea sistemului de coordonate în punctul (x,y) al sistemului de coordonate curent; 
    public abstract Color getColor() - întoarce culoarea de desenare curentă; 
    public abstract void setColor(Color c) - setează culoarea de desenare curentă; 
    public abstract Font getFont() - întoarce fontul curent; 
    public abstract void setFont(Font f) - setează fontul curent; 
    public abstract FontMetrics getFontMetrics() - întoarce metrica fontului curent; 
    public abstract void setFontMetrics(FontMetrics fm) - setează metrica fontului curent; 
    public abstract Rectangle getClipBounds() - întoarce dreptunghiul de decupare curent; 
    public abstract void setClip(int x, int y, int width, int height) - setează dreptunghiul de decupare curent; 
    public abstract void copyArea(int x, int y, int width, int height, int dx, int dy) - copiaza suprafața dreptunghiulara cu originea (x,y), lățimea width și înălțimea height într-o noua zonă de aceleași dimensiuni, având originea (x+dx, y+dy).. 
    public abstract void setPaintMode() - setează operația logică pe pixeli curentă la modul paint, adică desenarea se face peste fondul existent, folosinduse culoarea de desenare curentă, fără a lua în considerație culoarea fondului. 
    public abstract void setXORMode(color c1) - setează operațiile pe pixeli la modul XOR, adică se alternează pixelii de culoare curentă cu cei din culoarea c1; aceasta înseamnă că, dacă un pixel nou plasat are aceeași culoare curentă cu cea existentă anterior în același loc, ea va fi înlocuita cu c1; invers, dacă pixelul existent anterior avea culoarea c1, ea va fi înlocuită cu culoarea curentă. 
    public abstract void drawLine(int x1, int y1, int x2, int y2) - se traseaza o linie dreaptă din punctul de coordonate (x1,y1) până în punctul (x2,y2); 
    public abstract void drawRect(int x, int y, int width, int height) - se trasează un dreptunghi cu colțul din stânga sus în punctul (x,y), având lățimea width și înălțimea height; 
    public abstract void fillRect(int x, int y, int width, int height) - umple cu culoarea curentă interiorul dreptunghiului cu colțul din dreapta sus în punctul (x,y) și cu dimensiunile (width, height); 
    public abstract void clearRect(int x, int y, int width, int height) - șterge conținutul dreptunghiului de coordonate și dimensiuni specificate, umplându-l cu culoarea de fond; 
    public abstract void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) - trasează un dreptunghi cu colțurile rotunjite, unde arcWidth si arcHeight sunt respectiv diametrul orizontal și diametrul vertical al arcelor de rotunjire; 
    public abstract void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) - umple cu culoarea curentă interiorul unui dreptunghi cu colțurile rotunjite; 
    public abstract void draw3DRect(int x, int y, int width, int height, boolean raised) - desenează un dreptunghi astfel luminat, încât el apare că iese din suprafața de baza, dacă raised=true, sau este scufundat în aceasta suprafață, dacă raised=false; 
    public abstract void fill3dRect(int x, int y, int width, int height, boolean raised) - umple cu culoarea curentă interiorul unui dreptunghi tridimensional, luând în considerație și iluminarea; 
    public abstract void drawOval(int x, int y, int width, int height) - se desenează ovalul înscris în dreptunghiul cu colțul din stânga sus în punctul (x,y) și cu dimensiunile (width, height); 
    public abstract void fillOval(int x, int y, int width, int height) - se umple cu culoarea curentă conținutul ovalului înscris în dreptunghiul cu colțul stânga sus în punctul (x,y) și de dimensiuni (width, height); 
    public abstract void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle) - se desenează un arc circular sau eliptic, care se înscrie în dreptunghiul specificat. Putem să ne imaginăm că din elipsa înscrisă în acest dreptunghi și având centrul în centrul dreptunghiului, se trasează efectiv numai arcul care începe de la unghiul startAngle și se extinde pe un unghi egal cu arcAngle. Unghiurile sunt măsurate în grade. Unghiul 0 este corespunzator poziției de la ora 3 a acului de ceasornic, iar sensul pozitiv al unghiurilor este cel contra acelor de ceasornic; 
    public abstract void fillArc(int x, int y, int width, int height, int startAngle, int arcAngle) - umple cu culoarea curentă sectorul mărginit de arcul specificat prin parametri și de razele de la capete; 
    public abstract void drawPolyline(int[] xPoints, int[] yPoints, int nPoints) - trasează o linie frântă, care trece prin punctele ale căror coordonate (x,y) sunt date în tabelele xPoints și yPoints; numărul de puncte este nPoints; 
    public abstract void drawPoligon(int[] xPoints, int[] yPoints, int nPoints) - trasează un poligon cu nPoints vârfuri, situate în punctele ale căror coordonate (x,y) sunt date în tabelele xPoints și yPoints; 
    public abstract void drawPoligon(Poligon p) - trasează poligonul specificat ca argument; 
    public abstract void fillPoligon(int[] xPoints, int[] yPoints, int nPoints) - umple cu culoarea curenta un poligon cu nPoints vârfuri, situate în punctele ale căror coordonate (x,y) sunt date în tabelele xPoints și yPoints; 
    public abstract void fillPoligon(Poligon p) - umple cu culoarea curentă poligonul specificat ca argument; 
    public abstract void drawString(String str, int x, int y) - trasează șirul de caractere str, folosind fontul și culoarea curente; baza primului caracter al șirului este în punctul de coordonate (x,y); 
    public abstract void drawString(AttributedCharacterIterator iterator, int x, int y) - trasează un șir de caractere conținute în obiectul iterator, care specifică nu numai caracterele propriuzise ci și fontul fiecărui caracter; baza primului caracter este în punctul de coordonate (x,y); 
    public abstract void drawChars( char[] data, int offset, int length, int x, int y) - trasează length caractere din tabloul data, începând cu caracterul cu indicele offset; baza primului caracter se găsește în punctul de coordonate (x,y); fontul și culoarea sunt cele curente; 

Metodele paint si repaint

Pentru desenarea pe suprafața unei componente, în clasa java.awt.Component (rădăcina ierarhiei de clase a componentelor) există metoda
    public void paint(Graphics g)
Această metodă traseaza efectiv desenul, folosind în acest scop contextul grafic g primit ca argument. Contextul grafic nu este instanțiat de către programator, ci este transmis acestei metode de către mașina virtuală Java. În schimb, metoda paint trebuie redefinită în program pentru orice componentă pe care dorim să se traseze un desen. În aceast scop se folosesc în metoda paint metodele de desenare ale clasei Graphics.

Metoda paint nu este invocată explicit în program. Ea este invocată implicit (de către mașina virtuală Java) atunci când componenta respectivă este afișată pe ecran sau își modifică dimensiunile și/sau poziția. Dacă, totuși, programatorul dorește să solicite explicit desenarea, folosește metoda
    public void repaint()
Aceasta metodă care există, de asemenea, în clasa java.awt.Component,  nu trebuie redefinită, singurul ei rol fiind de a apela metoda paint.
 
Exemplul 1: 

În fișierul Desen.java este dat un exemplu de aplicație simplă, în care se testează diferite metode ale clasei Graphics. În acest scop, s-a creat clasa PanouDesen ca o extensie a clasei Canvas. În această clasă, a fost redefinită metoda paint(), astfel încât să se traseze diferite desene: un dreptunghi gol, un dreptunghi plin, un dreptunghi gol cu colțurile rotunjite, un dreptunghi plin cu colțurile rotunjite, un oval gol, un oval plin, o linie frântă și un poligon. S-au testat în acest fel metodele clasei Graphics. În fereastra aplicației s-a introdus o instanță a clasei PanouDesen. 
 
/* Testarea clasei Graphics 
   In fereastra aplicatiei iug se introduce suprafata de desenare
   panouDesen din clasa PanouDesen, care este derivata din clasa
   Canvas
   In clasa PanouDesen, metoda paint() este redefinita, astfel
   incat sa traseze mai multe desene, testandu-se  metodele
   clasei Graphics
*/

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

class Desen {
   static Sfarsit sfarsit=new Sfarsit();
   static IUG iug=new IUG("Exemplu de desenare");

   static class IUG extends JFrame {
    PanouDesen panouDesen;

    IUG(String titlu) {
     super(titlu);
     addWindowListener(sfarsit);
     panouDesen=new PanouDesen();
     setLocation(200,150);
     getContentPane().add(panouDesen);
     setSize(220,200);
     setVisible(true);
    }
  }
 

   static class Sfarsit extends WindowAdapter {
      public void windowClosing(WindowEvent e) {
         System.exit(0);
      }
   }

   static class PanouDesen extends Canvas {

      public void paint(Graphics g) {
         setBackground(Color.white);
         g.setColor(Color.black);
         g.drawRect(5,5,40,25);
         g.drawRect(50,5,40,25);
         g.setColor(Color.blue);
         g.fillRect(50,5,40,25);
         g.setColor(Color.red);
         g.drawRoundRect(100,5,40,25,10,10);
         g.drawRoundRect(150,5,40,25,10,10);
         g.setColor(Color.green);
         g.fillRoundRect(150,5,40,25,10,10);
         g.drawOval(5,40,40,25);
         g.drawOval(50,40,40,25);
         g.setColor(Color.magenta);
         g.fillOval(50,40,40,25);
         g.setColor(Color.black);
         g.drawArc(100,40,40,25,-5,130);
         g.drawArc(150,40,40,25,-5,130);
         g.setColor(Color.green);
         g.fillArc(150,40,40,25,-5,130);
         g.setColor(Color.black);
         int[] xlf={5,30,55,80,100,125}, ylf={100,70,85,75,75,80};
         g.drawPolyline(xlf,ylf,6);
         int[] xp={15,50,75,65,55}, yp={100,90,95,120,150};
         g.drawPolygon(xp,yp,5);
         g.setColor(Color.yellow);
         g.fillPolygon(xp,yp,5); 
      }

   }

   public static void main(String args[]) {
   }
}


 
Exemplul 2:

În fișierul GraficeFunctii.java este dat un exemplu de aplicație pentru trasarea graficelor unor funcții. În fereastra aplicației s-au pus: 
    - panoul fct, care conține o listă de selectie a funcției de reprezentat și trei câmpuri de text, în care se introduc de la tastatură marginile inferioară și superioară a intervalului în care se trasează funcția (sub forma de numere reale) și numărul de subintervale în care se împarte acesta; 
    - suprafața de afișare gr, pe care se trasează graficul; 
    - eticheta mesaj, în care se afișează eventualele mesaje de eroare. 
Pentru panoul fct a fost creată clasa Functii, derivată din clasa JPanel și s-a folosit gestionarul de poziționare GridLayout. 
Pentru suprafața de afișare s-a creat clasa Grafic, derivată din clasa Canvas. 
Clasa Actiuni interceptează evenimentele generate de fereastra principală,  câmpurile de text și lista de selecție a funcțiilor. În acest scop, ea este derivată din clasa WindowAdapter și implementează interfețele ActionListener și ItemListener. 
Trasarea graficului se face sub forma unei linii frânte, folosind metoda drawPoliline(int x[],int y[],int nrPuncte), în care vectorii x și y conțin coordonatele punctelor prin care trece curba. Scările de reprezentare pe cele două axe se aleg automat, astfel încât graficul să ocupe întreaga suprafață de desenare. În acest scop, se calculează mai întâi valorile reale ale funcției de reprezentat, completându-se cu ele vectorul real valy. Se determină apoi ymax și ymin, iar dimensiunile suprafeței de desenare se determină prin metodele getWidth() și getHeight(), iar xmin și xmax sunt date. Folosind aceste date se calculează scările pe x și y, după care se calculează coordonatele pe desen ale punctelor, completându-se astfel vectorii x și y. Aceste calcule se fac în metoda calcul() din clasa Actiuni, iar trasarea graficului se face în metoda paint() din clasa Grafic. 
 
/* Aplicatie de trasare a graficelor unor functii.
   Se selecteaza functia de trasat si se introduc valorile marginilor
   intervalului de reprezentare.
   Trasarea functiei se face fie cand se selecteaza o noua functie,
   fie cand este selectat unul din campurile de text si se apasa 
   tasta <Enter>.
   Daca se introduc date gresite, nu se traseaza functia, ci apare un
   mesaj de eroare
*/

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

class GraficeFunctii {
  static Actiuni act=new Actiuni();
  static Functii fct=new Functii();
  static Grafic gr=new Grafic();
  static Label mesaj=new Label();
  static boolean trasare=false;
  static int nrPasi=200; // numarul de subintervale
  static int nrNoduri=nrPasi+1; // numarul de puncte ale graficului
  static int indFunc;  // indicele functiei care se reprezinta grafic
    // Tabele de coordonate ale punctelor graficului functiei
  static int x[]=new int[nrNoduri], y[]=new int[nrNoduri];
    // inaltimea si latimea suprafetei de desenare si pozitiile axelor
  static int inaltime, latime, xord, yabsc; 
 

  public static void main(String args[]) {
    JFrame fp=new JFrame("Grafice de functii");
    Container cp=fp.getContentPane();
    cp.add(fct,"North");
    cp.add(gr,"Center");
    gr.setBackground(Color.cyan);
    cp.add(mesaj,"South");
    mesaj.setBackground(Color.green);
    fp.addWindowListener(act);
    fp.setSize(500,500);
    fp.setVisible(true);
  }

  // Introducerea datelor si calcularea coordonatelor 

  static class Functii extends JPanel {
    // Componenta de selectare a functiei
    JLabel lab=new JLabel("Alegeti functia de reprezentat");
    Choice f=new Choice();
    // Campurile de text pentru introducerea marginilor intervalului
    // si numarului de subintervale
    JTextField inf=new JTextField("-6.28");
    JTextField sup=new JTextField("6.28");
    JTextField pasi=new JTextField("200");

    // Constructorul clasei Functii
    Functii() {
      // Adaugarea de functii la lista de optiuni
      f.add("x^2");
      f.add("x^3");
      f.add("log(1+x^2)-1");
      f.add("sin(x)");
      f.add("cos(x)");
      f.add("exp(-abs(0.1*x))*cos(x)");
      f.add("cos(x)/sqrt(1+x*x)");
      // Setarea gestionarului de pozitionare
      setLayout(new GridLayout(4,2));
      // Adaugarea componentelor
      add(new Label("Alegeti functia de reprezentat: "));
      add(f);
      add(new Label("Marginea inferioara a intervalului: "));
      add(inf);
      add(new Label("Marginea superioara a intervalului: "));
      add(sup);
      add(new Label("Numarul de subintervale pentru trasare: "));
      add(pasi);
      // Adaugarea la componente a interceptorului de evenimente
      inf.addActionListener(act);
      sup.addActionListener(act);
      pasi.addActionListener(act);
      f.addItemListener(act);
    }

    // Calcularea valorii functiei in punctul de abscisa x
    double func(double x) {
      switch(indFunc) {
        case 0: return x*x;
        case 1: return x*x*x;
        case 2: return Math.log(1+x*x)-1;
        case 3: return Math.sin(x);
        case 4: return Math.cos(x);
        case 5: return Math.exp(-Math.abs(0.1*x))*Math.cos(x);
        case 6: return Math.cos(x)/Math.sqrt(1.0+x*x);
      }
      return 0.0;
    }
 

  }

  // Captarea si tratarea evenimentelor
  static class Actiuni  extends WindowAdapter
               implements ActionListener, ItemListener {

    public void windowClosing(WindowEvent e) {
      System.exit(0);
    }
    public void actionPerformed(ActionEvent e) {
      reprezinta();
    }
    public void itemStateChanged(ItemEvent e) {
      reprezinta();
    }
    public void reprezinta() {
      try {
        calcul();//Se calculeaza coordonatele punctelor graficului
        gr.repaint();//Se traseaza graficul functiei
      }
      catch(Exception e) {
        mesaj.setText("eroare: "+e);
      }
    }
    // Calcularea coordonatelor tuturor punctelor graficului
    void calcul() throws Exception {
      double xmin,xmax,ymin,ymax,pas,scarax,scaray;
      // Initializari
      mesaj.setText("");
      trasare=false;
      gr.repaint();
      // Preluarea datelor introduse in campurile de text
      int nrP=Integer.parseInt(fct.pasi.getText());
      if(nrP<1) throw new Exception("Numar de intervale incorect");
      if(nrP != nrPasi) {
        nrPasi=nrP;
        nrNoduri=nrPasi+1;
        x=new int[nrNoduri];
        y=new int[nrNoduri];
      }
      xmin=Double.parseDouble(fct.inf.getText()); // marginea din stanga
      xmax=Double.parseDouble(fct.sup.getText()); // marginea din dreapta
      if(xmin==xmax) throw new Exception("xmin==xmax");
      pas=(xmax-xmin)/nrPasi; // Lungimea subintervalului 
      double valy[]=new double[nrNoduri];// tabloul ordonatelor
      // Preluarea indicelui functiei selectate
      indFunc=fct.f.getSelectedIndex(); // se afla indicele functiei
      // Determinarea dimensiunilor suprafetei de desenare
      inaltime=gr.getHeight();  // inaltimea suprafetei de desenare
      latime=gr.getWidth();  // latimea suprafetei de desenare
      // Calcularea ordonatelor punctelor graficului
      for (int i=0; i<nrNoduri; i++) valy[i]=fct.func(xmin+i*pas);
      // Determinarea valorilor minima si maxima ale lui y
      ymin=valy[0];
      ymax=valy[0];
      for (int i=1; i<nrNoduri; i++) {
        if(valy[i]>ymax) ymax=valy[i];
        if(valy[i]<ymin) ymin=valy[i];
      }
      // Determinarea scarilor de reprezentare pe cele doua directii
      scarax=latime/(xmax-xmin);
      scaray=inaltime/(ymax-ymin);
      // Calcularea coordonatelor de pe desen ale punctelor graficului
      for(int i=0; i<nrNoduri; i++) {
         y[i]=inaltime-(int)Math.round((valy[i]-ymin)*scaray);
         x[i]=(int)Math.round(i*pas*scarax);
      }
      // Determinarea pozitiilor pe desen ale axelor de coordonate
      yabsc=inaltime+(int)Math.round(ymin*scaray);
      xord=(int)Math.round(-xmin*scarax);
      trasare=true;
    }
  }

  // Componenta pe care se traseaza desenul
  static class Grafic extends Canvas {
    // Metoda de trasare a graficului
    public void paint(Graphics g) {
      if(!trasare) return;
      // Trasarea axelor de coordonate
      g.setColor(Color.blue);
      if(yabsc>=0 && yabsc<=inaltime)
        g.drawLine(0,yabsc,latime,yabsc);
      if(xord>=0 && xord<=latime)
        g.drawLine(xord,0,xord,inaltime); 
      // Trasarea curbei (sub forma de linie franta)
      g.setColor(Color.red);
      g.drawPolyline(x,y,nrNoduri);
    }
  }
}



© Copyright 2000 - Severin BUMBARU, Universitatea "Dunărea de Jos" din Galați