Java Servlets – grafické efekty a cache

8. dubna 2004

V predchádzajúcich článkoch sme vytvorili servlety, ktoré vytvárajú alebo navzájom kombinujú grafické prvky. V tomto článku sa pozrieme na to, ako servlet môže pridávať k obrázkom rôzne grafické efekty. Napríklad môže zmenšiť čas potrebný na prenos dát tým, že zmenší veľkosť obrázku pred jeho odoslaním klientovi. Alebo môže k existujúcemu obrázku pridať tieň, imitujúc tak tlačidlo. Vytvoríme si tiež príklad, v ktorom servlet použijeme na konverziu farebného obrázku na obrázok v 256 stupňoch šedej farby.

Konverzia s využitím šedej škály

Nasledujúci príklad ukazuje servlet, ktorý pred odoslaním skonvertuje obrázok použijúc 256 stupňov šedej farby. Rozdiel v porovnaní s predchádzajúcimi servletmi je v tom, že servlet nevytvára konvertovaný obrázok pomocou externého grafického kontextu. Namiesto toho použije špeciálny filter.

GreyScaleServlet.java

import java.awt.*;
import java.awt.image.*;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import Acme.JPM.Encoders.*;
public class GreyScaleServlet extends HttpServlet {
 public void doGet
           (HttpServletRequest req, HttpServletResponse res)
                      throws ServletException, IOException {
  res.setContentType(„image/gif“);
  ServletOutputStream out = res.getOutputStream();
  // získame info o umiestnení obrázku
  String source = req.getPathTranslated();
  if (source == null) {
   throw new ServletException(„Chyba v umiestnení obrázku“);
  }
  /* vytvoríme viacnásobne použiteľný frame, pričom
  tentokrát nepotrebujeme volať metódu addNotify() */

  Frame frame = new Frame();
  // natiahneme obrázok do pamäte
  Image image = Toolkit.getDefaultToolkit().getImage(source);
  MediaTracker mt = new MediaTracker(frame);
  mt.addImage(image, 0);
  try {
   mt.waitForAll();
  }
  catch (InterruptedException e) {
   getServletContext().log(„Chyba pri načítaní obrázka“, e);
   throw new ServletException(e.getMessage());
  }
  // zistíme rozmery obrázku
  int width = image.getWidth(frame);
  int height = image.getHeight(frame);
  // obrázok preženieme cez filter
  Image filtered = frame.createImage(
                  new FilteredImageSource(image.getSource(),
                  new GreyScaleFilter()));
  // obrázok zakódujeme a pošleme
  GifEncoder encoder = new GifEncoder(filtered, out);
  encoder.encode();
 }
}

Tento servlet nevyužíva metódu createImage(int,int) triedy Component tak, ako sme boli zvyknutý. Namiesto toho však používa metódu createImage(ImageProducer). Tento „producent obrázkov“ je vytvorený prostredníctvom triedy FilteredImageSource, ktorej predáme odkaz na obrázok a vlastnú implementáciu filtra GreySscaleFilter. Tento filter konvertuje každý jeden pixel na jeho náprotivok použitím prevodného algoritmu a 256-ovej stupnice.

GreyScaleFilter.java

import java.awt.*;
import java.awt.image.*;
public class GreyScaleFilter extends RGBImageFilter {
 public GreyScaleFilter() {
  canFilterIndexColorModel = true;
 }
 /* skonvertujeme farebné pixle pomocou
 algoritmu odpovedajúcemu RGB špecifikácii */

 public int filterRGB(int x, int y, int pixel) {
  // získame priemernú RGB intenzitu
  int red = (pixel & 0x00ff0000) >> 16;
  int green = (pixel & 0x0000ff00) >> 8;
  int blue = pixel & 0x000000ff;
  // aplikujeme vážený priemer
  int rgb = (int) (0.299*red + 0.587*green + 0.114*blue);
  // vrátime hodnotu pre každý RGB komponent
  return (0xff << 24) | (rgb << 16) | (rgb << 8) | rgb;
 }
}

Tento filter obdrží RGB hodnotu každého pixla a vráti novú prefiltrovanú hodnotu. Nastavením canFilterIndexColorModel na true určíme, že filter resp. metóda filterRGB(), bude operovať nad tabuľkou farebných indexov objektu IndexColorModel. V opačnom prípade bude postupovať pixel za pixlom a vykonávať konverziu.

RGB hodnota pixla je daná 32 bitovým číslom, kde prvý oktet reprezentuje hodnotu alfa kanálu (transparentnosť), druhý oktet je intenzita červenej, tretí oktet je intenzita zelenej a štvrtý oktet je intenzita modrej. Ak chcete skonvertovať pixel použitím šedej stupnice a zároveň poznáte jeho hodnoty RGB, je nutné tieto hodnoty nastaviť na identické, to znamená spriemerovať ich.

Avšak vzhľadom na skutočnosť ako ľudské oko vníma farby a ich intenzitu (spolu s inými faktormi), nestačí vykonať jednoduchý aritmetický priemer jednotlivých farebných zložiek, ale odporúča sa následne vykonať vážený priemer v definovanom pomere (Red)0,299 : (Green)0,587 : (Blue)0,114. Nasleduje ukážka práce nášho príkladu:

Originálny obrázok
Originálny obrázok

Obrázok prehnaný cez filter
Obrázok prehnaný cez filter

Cache-ovanie konvertovaného obrázku

Tvorba a kódovanie grafiky môžu byť z hľadiska náročnosti na zdroje (pamäť a CPU) veľmi drahé. Zvlášť ak ide o servery s veľkou návštevnosťou. Určitým riešením (a tiež dobrým mravom), je nevykonávať všetku prácu pri každej požiadavke, ale vytvoriť všetko potrebné len prvý krát a pri ďalších požiadavkách poskytnúť už hotový výsledok. Riešením teda môže byť uložiť obrázok dočasne do pamäte (cache).

Upravme si teda predchádzajúci príklad tak, aby pred prvým odoslaním uložil konvertovaný obrázok do cache. Urobíme to tak, aby sa každý konvertovaný obrázok uložil do hash tabuľky pod svojím menom. Ako prvé je nutné vytvoriť inštanciu Hashtable, mimo metódy doGet(). Aby sme mohli hash tabuľku naplniť, musíme z konvertovaného obrázka vytvoriť ByteArrayOutputStream, ten následne zakódovať a uložiť do tabuľky. Potom môžeme zakódovaný prúd bajtov zapísať do objektu ServletOutputStream a tým ho poslať klientovi. Nasleduje upravený príklad.

GreyScaleServletCached.java

import java.awt.*;
import java.awt.image.*;
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import Acme.JPM.Encoders.*;
public class GreyScaleServletCached extends HttpServlet {
 // vytvoríme hash tabuľku
 Hashtable gifs = new Hashtable();
 public void doGet
           (HttpServletRequest req, HttpServletResponse res)
                      throws ServletException, IOException {
  res.setContentType(„image/gif“);
  ServletOutputStream out = res.getOutputStream();
  // získame info o umiestnení obrázku
  String source = req.getPathTranslated();
  if (source == null) {
   throw new ServletException(„Chyba v umiestnení obrázku“);
  }
  // ak skonvertovaný obrázok existuje, pošleme ho klientovi
  if (gifs.containsKey(source)) {
   ByteArrayOutputStream baos = (ByteArrayOutputStream)
                                           gifs.get(source);
   baos.writeTo(out);
   return;
  }
  // vytvoríme viacnásobne použiteľný frame
  Frame frame = new Frame();
  // natiahneme obrázok do pamäte
  Image image = Toolkit.getDefaultToolkit().getImage(source);
  MediaTracker mt = new MediaTracker(frame);
  mt.addImage(image, 0);
  try {
   mt.waitForAll();
  }
  catch (InterruptedException e) {
   getServletContext().log(„Chyba pri načítaní obrázka“, e);
   throw new ServletException(e.getMessage());
  }
  // zistíme rozmery obrázku
  int width = image.getWidth(frame);
  int height = image.getHeight(frame);
  // obrázok preženieme cez filter
  Image filtered = frame.createImage(
                  new FilteredImageSource(image.getSource(),
                  new GreyScaleFilter()));
  // obrázok zakódujeme, uložíme do Hashtable a pošleme
  ByteArrayOutputStream baos = new ByteArrayOutputStream();
  GifEncoder encoder = new GifEncoder(filtered, baos);
  encoder.encode();
  gifs.put(source, baos);
  baos.writeTo(out);
 }
}

Použitím týchto modifikácií je akýkoľvek obrázok, uložený v pamäti, vrátený klientovi veľmi rýchlo. Treba však myslieť na to, že cache-ovanie veľkého množstva obrázkov skonzumuje aj veľa pamäte. V prípade, že môže táto alternatíva nastať, je rozumné do kódu zapracovať algoritmus, ktorý bude do pamäte ukladať len niekoľko najčastejšie žiadaných obrázkov.

Na záver vám ponúkam použité súbory na stiahnutie.

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

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

Štítky: Články

Mohlo by vás také zajímat

Nejnovější

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *