Veľká väčšina ľudí je vizuálne orientovaná, to znamená, že ľahšie absorbujú grafické informácie prezeraním obrazov ako čítaním textu. Pravdepodobne by bolo veľmi ťažké nájsť web stránku, ktorá nejakým spôsobom nevyužíva grafiku. Určite platí to známe: „Jeden obrázok je viac ako tisíc slov.“ Všetky servlety, ktoré sme zatiaľ vytvorili, posielali iba textové informácie. Tentoraz sa to ale zmení.

Úvod do problematiky

Teoreticky nie je problém vytvoriť servlet, ktorý ako svoju odpoveď bude posielať obrázok, respektíve viac obrázkov. Nezáleží pritom, kde fyzicky tieto obrázky sú uložené, či je to v adresári na disku alebo na niektorom z databázových serverov. Jedinou podmienkou v tomto prípade je, aby servlet správne identifikoval a následne nastavil správny MIME typ posielanej odpovede. Využíva sa na to metóda setContentType().

Lenže tento spôsob vyžaduje dopredu pripravenú existenciu týchto obrázkov, čo nie je vždy možné. V tom prípade musí servlet grafiku vygenerovať, prípadne s ňou ešte manipulovať, skôr ako bude poslaná klientovi. Príkladom môžu byť analógové hodiny zobrazujúce na stránke aktuálny čas, alebo grafické počítadlo návštevnosti stránok. V prípade tých hodín by ste potrebovali 720 obrázkov (60 minút x 12 hodín) bez použitia sekundovej ručičky. Je jasné, že v tomto prípade je vhodnejšie vytvoriť servlet, ktorý dynamicky vytvorí pozadie hodín a samotné ručičky. Alebo servlet, ktorý využije existujúci statický obrázok pozadia hodín a dynamicky tvorí hodinové ručičky.

Predpokladajme, že máme obrázok vo forme surových dát predstavujúcich jednotlivé obrazové body. Tento obrázok chceme niekomu poslať tak, aby sa mu správne vykreslil, teda aby mal správne rozmery a umiestnenie. Ako to urobiť? Vieme, že obrázok je 24 bitový true-color (3 byty na pixel), a že je 100 pixlov vysoký a 100 pixlov široký. Ak ho pošleme po jednotlivých pixloch, pôjde o prúd dát vo veľkosti 30 000 bytov. Myslíte si, že to stačí? Zamyslite sa nad tým, ako príjemca môže vedieť, čo má s práve obdržanými dátami robiť. Odpoveď je krásne jednoduchá – nevie.

Vašou povinnosťou je totiž povedať mu, že posielate surové grafické dáta v true-color formáte. A tiež, že ich posielate riadok za riadkom, pričom jeden riadok je 100 pixlov široký. Na dôvažok v prípade, že ste použili kompresiu pre zmenšenie objemu prenášaných dát, musíte príjemcovi oznámiť aký typ kompresie bol použitý. Až na základe týchto informácií môže príjemca, ak je toho schopný, správne spracovať prijaté dáta. Toto však môže predstavovať problém.

Avšak tento problém už bol vyriešený a to viacerými spôsobmi. Každý grafický formát (GIF, JPEG, PNG, TIFF…) predstavuje jedno z možných riešení vyššie uvedeného problému, pričom definuje spôsob, ktorým sú grafické dáta zakódované. Samozrejme, že každé z riešení má svoje výhody ale aj obmedzenia. Napríklad formát GIF sa ideálne hodí pre počítačovo generovanú grafiku, avšak maximálne do 256 farieb. Naproti tomu formát JPEG je vhodný pre fotografie, obsahujúce milióny farieb, a to vďaka stratovej kompresii.

Prečo o tom píšem? Totiž aspoň čiastočné pochopenie problematiky kódovania grafických formátov vám pomôže rýchlejšie pochopiť spôsob, akým servlety narábajú s grafickými informáciami. Pretože servlet na jednej strane síce môže poslať klientovi dopredu vyrobený obrázok, ale na druhej strane musí servlet, ktorý generuje a manipuluje s grafikou, najprv vytvoriť vnútornú interpretáciu daného obrazu, previesť prípadné transformácie a pred samotným odoslaním vykonať zakódovanie do príslušného formátu.

Praktická ukážka

Dosť bolo suchej teórie, nás v prvom rade zaujíma ako previesť vedomosti do praxe. Začneme celkom jednoducho, textom. Nie, nemusíte sa báť, tento článok je naozaj o grafike. Vytvoríme si obligátny HelloWorld servlet, generujúci známy pozdrav. Najprv samotný servlet, potom vysvetlenie.

HelloWorld.java:

import java.io.*;
import java.awt.*;
import javax.servlet.*;
import javax.servlet.http.*;
import Acme.JPM.Encoders.GifEncoder;
public class HelloWorld extends HttpServlet {
 public void doGet(HttpServletRequest req,
                                  HttpServletResponse res)
      throws ServletException, IOException {
  // binárny výstup!
  ServletOutputStream out = res.getOutputStream();
  Frame frame = null;
  Graphics g = null;
  try {
   // vytvoríme frame
   frame = new Frame();
   frame.addNotify();
   // vytvoríme image
   Image image = frame.createImage(400, 60);
   g = image.getGraphics();
   // zapíšeme reťazec HelloWorld!
   g.setFont(new Font(„Serif“, Font.ITALIC, 48));
   g.drawString(„Hello World!“, 10, 50);
   // vytvorený image zakódujeme do formátu GIF
   // a pošleme ho klientovi
   res.setContentType(„image/gif“);
   GifEncoder encoder = new GifEncoder(image, out);
   encoder.encode();
  }
  finally {
   // uvoľníme zdroje
   if (g != null) g.dispose();
   if (frame != null) frame.removeNotify();
  }
 }
}

Napriek tomu, že tento servlet využíva balíček java.awt.*, nikdy nezobrazí na obrazovke servera žiadne okno. Všetka práca sa vykonáva v režime off-screen. Základom je vytvoriť objekt Image, získať jeho grafický kontext, zapísať doň zmeny, výsledok zakódovať a odoslať klientovi. V Jave je síce obrázok reprezentovaný triedou java.awt.Image, ale nie je možné ho získať priamo cez konštruktor. Je nutné použiť niektoré factory metódy, napríklad createImage() triedy Component alebo getImage() triedy Toolkit. V našom prípade musíme použiť prvú z nich, keďže vytvárame nový obrázok. Skôr ako však vytvoríme image, musí existovať objekt java.awt.Frame a jeho prepojenie na existujúci natívny grafický systém prostredníctvom metódy addNotify().

Potom, čo sa zapíše reťazec HelloWorld! do grafického kontextu, nastavíme MIME typ odpovede na „image/gif“, keďže budeme posielať obrázok vo formáte GIF. Na zakódovanie objektu image a jeho odoslanie klientovi je použitý GIF encoder, ktorý napísal Jef Poskanzer. Je voľne dostupný na http://www.acme.com. Postup je taký, že pri vytváraní objektu GifEncoder mu ako parametre predáme objekty Image a ServletOutputStream. Následne zavoláme metódu encode() a obrázok je zakódovaný a poslaný klientovi.

Po poslaní obrázka je dobré uvolniť používané zdroje. Tieto zdroje by boli síce nakoniec uvoľnené pri spustení garbage collectora, ale ich okamžité uvoľnenie je vhodné najmä pri menej robustných systémoch. Všimnite si, že kód, ktorý uvoľňuje zdroje, je umiestnený vo finally bloku. To znamená, že sa vykoná aj vtedy, ak servlet vyhodí výnimku.

Ukážka HelloWorld servletu

Druhý príklad, ktorý si vytvoríme, bude o niečo zaujímavejší. Avšak nie až tak komplikovaný, aby mu bolo ťažké porozumieť. Pôjde o dynamické generovanie stĺpového grafu, ktorý porovnáva predajnosť dvoch značiek áut v jednotlivých rokoch. Keďže naprogramovať všetky triedy potrebné na vygenerovanie grafu by presiahlo rozsah tejto časti, využijeme voľne prístupné triedy.

AutoChart.java:

import java.awt.*;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import Acme.JPM.Encoders.GifEncoder;
import javachart.chart.*;
public class AutoChart extends HttpServlet {
 static final int WIDTH = 450;
 static final int HEIGHT = 320;
 public void doGet(HttpServletRequest req,
                                  HttpServletResponse res)
      throws ServletException, IOException {
 ServletOutputStream out = res.getOutputStream();
 Frame frame = null;
 Graphics g = null;
 try {
  //vytvoríme stĺpový graf
  BarChart chart = new BarChart(„Audi a Škoda“);
  //určíme mu názov
  chart.getBackground().setTitleFont(
                         new Font(„Serif“, Font.PLAIN, 24));
  chart.getBackground().setTitleString(
                                „Porovnanie Audi a Škoda.“);
  //zobrazíme ho, umiestnime ho a nastavíme mu legendu
  chart.setLegendVisible(true);
  chart.getLegend().setLlX(0.4);
  chart.getLegend().setLlY(0.75);
  chart.getLegend().setIconHeight(0.04);
  chart.getLegend().setIconWidth(0.04);
  chart.getLegend().setIconGap(0.02);
  chart.getLegend().setVerticalLayout(false);
  //pridáme dáta a popisy
  double[] audiData = {450,520,740,563,850};
  chart.addDataSet(„Audi“, audiData);
  double[] skodaData = {1435,1650,1555,1440,1595};
  chart.addDataSet(„Škoda“, skodaData);
  String[] labels = {„1999″,“2000″,“2001″,“2002″,“2003“};
  chart.getXAxis().addLabels(labels);
  //nastavíme farbu stĺpcov
  chart.getDatasets()[0].getGc().setFillColor(Color.red);
  chart.getDatasets()[1].getGc().setFillColor(Color.blue);
  //pomenujeme osi grafu
  chart.getXAxis().setTitleString(„Rok“);
  chart.getYAxis().setTitleString(„Predaj“);
  //nastavíme veľkosť
  chart.resize(WIDTH, HEIGHT);
  //vytvoríme frame
  frame = new Frame();
  frame.addNotify();
  //vytvoríme image
  Image image = frame.createImage(WIDTH, HEIGHT);
  g = image.getGraphics();
  //vykreslíme graf
  chart.drawGraph(g);
  //zakódujeme do GIFu a pošleme
  res.setContentType(„image/gif“);
  GifEncoder encoder = new GifEncoder(image, out);
  encoder.encode();
 }
 finally {
  //uvoľníme zdroje
  if (g != null) g.dispose();
  if (frame != null) frame.removeNotify();
 }
 }
}

Princíp je rovnaký ako v predchádzajúcom príklade. Vytvoriť off-screen image, získať jeho grafický kontext, zapísať doň graf, zakódovať vzniknutý obrázok do GIFu a poslať ho klientovi. Rozdiel je v tom, že najprv vytvoríme stĺpový graf, z ktorého neskôr vytvoríme obrázok. Na internete môžete nájsť viacero voľne dostupných balíčkov na tvorbu grafov (niektoré sú skutočne voľné a iné je možné použiť zadarmo len na nekomerčné účely). Čo sa týka dát, potrebných na naplnenie grafu, myslím, že je jasné, že v reálnom nasadení by boli uložené a získavané z databázy alebo iného dátového zdroja. Pre jednoduchosť som ich však vložil priamo do zdrojového kódu.

Dynamicky generovaný graf

V druhom príklade som využil balíček na generovanie grafov spoločnosti Visual Engineering. Stiahnuť ho môžete z adresy http://www.ve.com/kavachart/kc4.2b.servlet.zip (3,9 MB). Na nekomerčné účely je zdarma. Rovnako si môžete stiahnuť balíček, v ktorom sú príklady z tohto článku.

Starší komentáře ke článku

Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.

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

Odpovědět