V predchádzajúcom článku sme zamerali naše úsilie na tvorbu jednoduchých obrázkov. V tomto článku si skúsime ukázať, ako kresliť do už existujúcich obrázkov a ako kombinovať vopred pripravené obrázky do väčších celkov. V závere si tiež ukážeme, ako zabezpečiť správnu obsluhu výnimiek, ktoré môžu vzniknúť v servletoch generujúcich grafiku.

Zapisovanie do obrázka

Po zamyslení sa nad príkladmi z predchádzajúcej časti sa môže klamlivo zdať, že jedným zo spôsobov ako zapisovať do existujúceho obrázku, je získať objekt Image prostredníctvom Toolkit.getDefaultToolkit().getImage(imagename). Následne získať jeho grafický kontext cez getGraphics() metódu, a potom zapísať do tohto kontextu požadovanú informáciu. Na pohľad to vyzerá byť „nice and easy“ avšak pravda je taká, že nemôžete použiť metódu getGraphics() nad objektom Image, ak nebol vytvorený prostredníctvom metódy createImage() triedy Component

Ako teda problém šikovne riešiť? Začiatok bude rovnaký; musíte získať objekt Image a povedať mu, aby sa zapísal do iného vopred pripraveného grafického kontextu. Až potom môžete využiť tento kontext a zapisovať doň. Aby som teda dlho nevysvetľoval, ukážeme si to na príklade.

TopSecretImage.java

import java.awt.*;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import Acme.JPM.Encoders.GifEncoder;
public class TopSecretImage extends HttpServlet {
 Frame frame = null;
 Graphics g = null;
 public void init(ServletConfig config)
                throws ServletException {
  super.init(config);
  //vytvoríme viacnásobne použiteľný frame
  frame = new Frame();
  frame.addNotify();
 }
 public void doGet(
          HttpServletRequest req, HttpServletResponse res)
          throws ServletException, IOException {
  ServletOutputStream out = res.getOutputStream();
  try {
   //získame info o umiestnení obrázka
   String source = req.getPathTranslated();
   if (source == null) {
    throw new ServletException(„Chyba v hľadaní obrázka“);
   }
   //natiahneme obrázok do pamäte
   MediaTracker mt = new MediaTracker(frame);
  Image image = Toolkit.getDefaultToolkit().getImage(source);
   mt.addImage(image, 0);
   try {
    //spustíme načítanie
    mt.waitForAll();
   }
   catch (InterruptedException e) {
    getServletContext().log(„Chyba pri načítaní obrázka, e“);
    throw new ServletException(e.getMessage());
   }
   /* vytvoríme nový grafický kontext s
   rovnakými rozmermi aké má obrázok */
   int w = image.getWidth(frame);
   int h = image.getHeight(frame);
   Image offscreen = frame.createImage(w, h);
   g = offscreen.getGraphics();
   //vložíme obrázok do graf. kontextu
   g.drawImage(image, 0, 0, frame);
   //do kontextu zapíšeme „Top Secret!“
   g.setColor(Color.RED);
   g.setFont(new Font(„Monospaced“, Font.BOLD, 30));
   g.drawString(„Top Secret!“, 60, 80);
   //zakódujeme a pošleme klientovi
   res.setContentType(„image/gif“);
   GifEncoder encoder = new GifEncoder(offscreen, out);
   encoder.encode();
  }
  finally {
   //uvoľníme zdroje
   if (g != null) g.dispose();
  }
 }
 //uvoľníme zdroje
 public void destroy() {
  if (frame != null) frame.removeNotify();
 }
}

Popíšme si jednotlivé kroky bližšie. Na začiatku v metóde init() vytvoríme Frame, čo je niečo ako malovacie plátno. Tým zabezpečíme, že bude vytvorený pri prvej požiadavke na servlet. Pri ostatných požiadavkách je plátno už vytvorené, čím sa znižuje čas potrebný na vytvorenie odpovede. V druhom kroku získame názov a umiestnenie predpripraveného obrázka pomocou metódy getPathTranslated(), ktorá transformuje informáciu za názvom servletu v URL do skutočnej cesty. Myslím, že z obrázka je to jasne viditeľné. Nasleduje načítanie obrázka do pamäte, s využitím triedy MediaTracker. Potom získame informáciu o výške a šírke načítaného obrázku, vytvoríme nový objekt triedy Image s rovnakými rozmermi, a získame jeho grafický kontext. Nasledovný obrázok ukazuje skutočné umiestnenie predpripraveného obrázka. Samozrejme, že daná adresa je relatívna k dokument rootu servera. Fyzicky je umiestnený v $CATALINA_HOME/webapps/examples/images.

Ukážka skutočného umiestnenia súboru xfiles-logo.png

Teraz nám už nič nebráni, aby sme do prázdneho obrázku vložili ten, pôvodne načítaný v pamäti. Všetky nasledovné kroky by vám už mohli byť jasné, pretože sme ich použili v predchádzajúcej časti. Všimnite si, akým spôsobom sú ošetrené výnimky. Niektoré z nich sú logované, aby mohli byť neskôr analyzované. Keďže servlet vracia iba obrázok, je problém získať nejaké textové informácie o vzniknutej výnimke. Tento obrázok ukazuje výsledok nášho príkladu.

Ukážka výsledku prvého príkladu

Skladanie obrázkov

Už vieme, že servlet môže do predpripravených obrázkov zapisovať napríklad text. Nevieme však, že servlet môže kombinovať viacero predpripravených obrázkov do jedného výsledného obrazu. Technika je však veľmi podobná. Najprv je potrebné natiahnuť jednotlivé obrázky do pamäte, a následne ich zapísať do vhodne vytvoreného Image objektu, ktorý po zakódovaní pošleme klientovi. Teoreticky to vyzerá jednoducho, ale v praxi je treba v súvislosti s tým vyriešiť niekoľko problémov. Vytvorme si ukážku grafického počítadla návštevnosti stránky.

ImageCounter.java

import java.awt.*;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import Acme.JPM.Encoders.GifEncoder;
public class ImageCounter extends HttpServlet {
 public static final String DIR = „/images/numbers“;
 public static final String COUNT = „9876543210“;
 public void doGet(
            HttpServletRequest req, HttpServletResponse res)
            throws ServletException, IOException {
  ServletOutputStream out = res.getOutputStream();
  Frame frame = null;
  Graphics g = null;
  try {
   /* zistíme hodnotu, ktorá sa má zobraziť,
   musí to byť samostatný reťazec bez mena,
   alebo sa použije hodnota COUNT */
   String count = (String) req.getQueryString();
   if (count == null) count = COUNT;
   //vytvoríme jednorozmerné pole objektov Images
   int countlen = count.length();
   Image images[] = new Image[countlen];
  //toto pole naplníme jednotlivými obrázkami
  for (int i = 0; i < countlen; i++) {
  String imageSrc = getServletContext().getRealPath(
      DIR + „/“ + count.charAt(i) + „.gif“);
  images[i] = Toolkit.getDefaultToolkit().getImage(imageSrc);
   }
   //vytvoríme frame
   frame = new Frame();
   frame.addNotify();
   //natiahneme obrázky do pamäte
   MediaTracker mt = new MediaTracker(frame);
   for (int i = 0; i < countlen; i++) {
   mt.addImage(images[i], i);
   }
   try {
    mt.waitForAll();
   }
   catch (InterruptedException e) {
    getServletContext().log(„Chyba pri načítaní obrázka“, e);
    throw new ServletException(e.getMessage());
   }
   /* kontrola na vzniknuté chyby, ktoré
   sa mohli objaviť pri načítaní obrázkov */
   if (mt.isErrorAny()) {
    /* ak máme problém, treba určiť,
    o ktorý obrázok(ky) sa jedná */
    StringBuffer errorChars = new StringBuffer();
    for (int i = 0; i < countlen; i++) {
     if (mt.isErrorID(i)) {
      errorChars.append(count.charAt(i));
     }
    }
    throw new ServletException(„Problém vznikol v: “ +
        errorChars.toString());
   }
   //zistíme celkovú veľkosť obrázkov
   int width = 0;
   int height = 0;
   for (int i = 0; i < countlen; i++) {
    width += images[i].getWidth(frame);
    height = Math.max(height, images[i].getHeight(frame));
   }
   /* vytvoríme prázdny Image s rozmermi
   odpovedajúcimi celkovej veľkosti obrázkov,
   a získame jeho grafický kontext */
   Image image = frame.createImage(width, height);
   g = image.getGraphics();
   //vložíme jednotlivé obrázky
   int xindex = 0;
   for (int i = 0; i < countlen; i++) {
    g.drawImage(images[i], xindex, 0, frame);
    xindex += images[i].getWidth(frame);
   }
   //výsledok zakódujeme a pošleme
   res.setContentType(„image/gif“);
   GifEncoder encoder = new GifEncoder(image, out);
   encoder.encode();
  }
  //uvoľníme zdroje
  finally {
   if (g != null) g.dispose();
   if (frame != null) frame.removeNotify();
  }
 }
}

Tento servlet je o niečo komplikovanejší ako predchádzajúci. V prvom rade musí servlet najprv zistiť, aké číslo má na grafickom počítadle zobraziť. Zistí to z objektu HttpServletRequest a v prípade že je NULL, použije prednastavenú hodnotu. Čo sa týka samotného počítania prístupov, touto problematikou sme sa už zaoberali v skorších častiach seriálu a musím vás odkázať na ne. Tu som pre jednoduchosť použil takýto mechanizmus, v praxi by si servlet musel sám evidovať počet prístupov, ideálne aj s prenosom medzi jednotlivými životnými cyklami. Algoritmus pokračuje tým, že pre každé číslo zadané v požiadavke na servlet, sa načíta príslušný obrázok. Aby som si to uľahčil, názov obrázku je tvorený len číslom, ktoré predstavuje. Nasleduje výpočet celkovej šírky, ktorý je súčtom šírok jednotlivých obrázkov. Čo sa týka výšky, použije sa maximálna hodnota. Získané hodnoty sa použijú na vytvorenie grafického kontextu, do ktorého sa jednotlivé obrázky zapíšu v poradí ako boli natiahnuté do pamäte. V závere sa uskutoční zakódovanie a odoslanie obrázka klientovi. Uvádzam pár ukážok:

1. variant grafického počítadla

… použije sa prednastavená hodnota

2. variant grafického počítadla

… a náhodne zvolené číslo

3. variant grafického počítadla

Na záver článku som zaradil možnosť stiahnuť si všetky potrebné zdrojáky, skompilované triedy a obrázky v jednom balíčku.

Žádný příspěvek v diskuzi

Odpovědět