/*
 * Kepek.java
 * a képkezelést technikailag (AWT-vel) megvalósító osztályok
 * by pts@fazekas.hu at 2001.03.16, hierarchized by pts@fazekas.hu at Sun Apr 22 16:29:47 CEST 2001
 * további doksi: a README-ben és e file legkülső class-ának fejkommentjében
 *
 * Kincskereső Kisgömböc (C) Early May 2001 by eNTitánok (Rév Szilvia,
 * Szabó Péter <pts@inf.bme.hu>, Szurdi Miklós, Weizengruber Attila).
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or   
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package eNTitanok.gfx;
import eNTitanok.gfx.OkosAWT;
import eNTitanok.gfx.OkosAWT.OkosFixCanvas;
import java.applet.Applet;
import java.awt.Canvas;
import java.awt.Dimension;
import java.awt.Component;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.Rectangle;
import java.awt.Image;
import java.awt.Font;
import java.awt.Toolkit;
import java.awt.MediaTracker;
import java.awt.image.PixelGrabber;
import java.awt.image.MemoryImageSource;
// import java.awt.image.BufferedImage; // nem használjuk, mert JDK1.2 kéne
import java.awt.image.ImageProducer;
import java.util.Vector;
import java.util.Hashtable;
import java.util.Enumeration;

/**
 * A képkezelést technikailag (AWT-vel) megvalósító osztályok.
 *
 * <P>Átkozott képkezelés! Minden baj, vesződség, inkompabitibilitás, lassúság és
 * bosszúság forrása.
 *
 * <P>Pedig nem kérek nehezet. Csak annyit, hogy szeretnék egy gyorsan
 * megjeleníthető, transparency-t is tartalmazó Image objektumot az adott
 * méretben. Nem, ezt Java 2-ben nem lehet.
 */
public class Kepek {

  /**
   * Nehany keresetlen szo a kepbetoltesrol.
   * Paint-tal keszitett BMP-t a Java nem tud betolteni. Punktum.
   * GIF-et es JPG-et be lehet tolteni.
   * Java Applet nem tolthet be a vinyorol akarhonnan kepet (SecurityException)
   * Java Applet tolthet be a sajat konyvtarabol kepet, akkor is, ha a sajat
   * konyvtara a vinyon van:
   *   this.getImage(this.getCodeBase(),"kepnev.gif");
   * a transparent GIF-et a Java jol tolti be.
   */
  public static class Kepbetolto extends MediaTracker {
    public static class SikertelenBetoltesException extends Error {}
    protected Hashtable ht; // filenev->Image; Hashtable csak !null->!null-t enged meg.
    protected Hashtable szk; // filenev->Vector(Kep); Hashtable csak !null->!null-t enged meg.
    protected Vector v; // num->filenev
    /**
     * Például "eNTitanok/kkkg/design/".
     */
    protected String resdir;
    public Kepbetolto() { this("eNTitanok/kkkg/design/")}
    public Kepbetolto(String resdir) {
      // `public' bugfix by pts@fazekas.hu at Sun Apr 22 16:33:30 CEST 2001
      super(OkosAWT.getMainComponent());
      this.resdir=resdir;
      ht=new Hashtable();
      szk=new Hashtable(); v=new Vector();
    }

    public void elfelejt(String filenev) {
      // csak mind_meglegyen után szabad hívni!!
      ht.remove(filenev);
    }

    public Kep betolt(String filenev) {
      // filenev ap.getCodeBase()-hez relatív...
      Image kep;
      if (!ht.containsKey(filenev)) {
        // if (ap.getCodeBase()==null) throw new NullPointerException();
        kep=OkosAWT.getImageBase(filenev);
        super.addImage(kep, v.size());
        v.addElement(filenev);
        ht.put(filenev, kep);
      } else kep=(Image)ht.get(filenev);
      Kep k=new Kep(filenev, kep);
      Vector szv;
      if ((szv=(Vector)szk.get(filenev))==null) szk.put(filenev, szv=new Vector());
      szv.addElement(k);
      return k;
    }

    /**
     * Egyetlen kép letöltődésére vár. Pontosan akkor true, ha sikerült.
     */
    public boolean egyreVar(Image image) {
      super.addImage(image, -42);
      try {
        super.waitForID(-42);
        Object balul[]=super.getErrorsID(-42);
        boolean ret=(balul==null || balul.length==0) && image.getHeight(null)>=0 && image.getWidth(null)>=0;
        super.removeImage(image, -42);
        return ret;
      } catch (Exception e) {
        return false;
      }
    }

    /**
     * az összes folyamatban levő betöltést végigviszi
     */
    public void mind_meglegyen() {
      // `public' bugfix by pts@fazekas.hu at Sun Apr 22 16:31:50 CEST 2001
      boolean balulp=false;
      try { super.waitForAll()}
        catch (InterruptedException e) { throw new SikertelenBetoltesException()}
      //balul=super.getErrorsAny();
      for (int i=v.size()-1;i>=0;i--) {
        Object balul[]=super.getErrorsID(i);
        String filenev=(String)v.elementAt(i);
        boolean ok=false;
        Image image=(Image)ht.get(filenev);
        if (balul!=null && balul.length>0) {
          // ha az applet/application mellett (ugyanabban a könyvtárban) nem
          // találtuk, akkor még mindig van esélyünk, hogy a .jar file-ban
          // benne van, azon belül az eNTitanok/kkkg/design/ könyvtárban.
          //
          // a getEroforras* (JDK1.1-ben) csak ott keresi a képeket, ahonnan
          // a KkkgAppl.class-t is betöltötte. Tehát a .jar file és a sima
          // file-ok közül csak az egyikben. (Tesztelve.)
          byte t[]=OkosAWT.getEroforrasByteTOf(OkosAWT.getMainClassLoader(), resdir+filenev);
          if (t!=null) {
            // System.err.println("Hi:"+filenev);
            Image ujImage=OkosAWT.getMainToolkit().createImage(t);
            OkosAWT.getMainComponent().prepareImage(ujImage,OkosAWT.getMainComponent());
            // ^^^ ez elengedhetetlen JDK1.1-nek
            if (egyreVar(ujImage)) {
              // ^^^ ez elengedhetetlen JDK1.2-nek és JDK1.3-nak
              // System.err.println(t.length);
              // eNTitanok.util.Idozito.alszik(1000);
              // System.err.println(ujImage.getHeight(null));
              ht.put(filenev,ujImage);
              // ^^^ bele _kell_ rakni, hogy mások is megkaphassák
              Vector szv=(Vector)szk.get(filenev);
              for (int h=0;h<szv.size();h++) ((Kep)szv.elementAt(h)).setImage(ujImage);
              ok=true;
            } /// IF
          } /// IF
        } else if (image.getHeight(null)>=0 && image.getWidth(null)>=0) {
          ok=true;
        } /// IF
        if (!ok) {
          java.lang.System.err.println("Letoltesi hiba kepfile='"+filenev+"'.");
          // OK: normál hibaüzenet, filnevekkel (kulon hash-ben)
          balulp=true;
        }
        super.removeImage(image, i);
      } // FOR
      if (balulp) throw new SikertelenBetoltesException();
      Enumeration e=szk.keys();
      while (e.hasMoreElements()) {
        String filenev=(String)e.nextElement();
        Vector szv=(Vector)szk.get(filenev);
        // System.err.println("siker: "+filenev);
        for (int h=0;h<szv.size();h++) ((Kep)szv.elementAt(h)).betoltve();
        // szk.remove(filenev);
      }
      v.setSize(0)// kiváltottuk a JDK1.2.2 `v.clear();'-jét
      szk.clear()// kiválthatnánk fent egyenkénti remove()-val, de működik
    }
  } /// class Kepbetolto

  public static class ButaKep {
    /* a ButaKep egy alapvetően nem módosítható képet testesít meg. A kép pixelei
     * egyenként lekérdezhetők (red, green, blue, alpha), és egy külön tömbben
     * módosíthatók. Miután a pixelek módosultak, a képet újra kell generálni.
     * A kép lehet transparent (áltlátszó, alpha!=255) és nem transparent is.
     * A képre getGraphics() nem kérhető!!
     */
    public static final int C_FEKETE=0, C_FEHER=0xFFFFFF,
      C_PIROS=0xFF0000, C_ZOLD=0x00FF00, C_KEK=0x0000FF,
      C_SARGA=0xFFFF00, C_LILA=0xFF00FF, C_TURKIZ=0x00FFFF,
      C_KEK1=0x000080, C_KEK3=0x004040;
    public static class GrabbingFailedException extends Error {}
    public static class MegToltodikException extends Error {}
    // public static final int winpal[]= 0,
    public static final int S_JONMEG=4, S_KEPOK=1, S_PIXOK=2, S_PIXKEPOK=3;
    protected int status=S_JONMEG;
    protected int width=-1, height=-1;
    protected Image kep; // !!
    protected int pixels[];
    protected String filenev;
    protected Component co;
    // ^^^ csak azért kell, hogy a .createImage-et hívhassuk
    //     Imp: remove, már mainComponent van

    public ButaKep(String filenev, Image kep, Component co) { this.filenev=filenev; this.kep=kep; this.co=co; }
    public int getWidth() { return width; }
    public int getHeight() { return height; }
    public int getStatus() { return status; }
    public Image getKep() { return kep; }
    public Image getImage() { return kep; }
    void setImage(Image image) { this.kep=image; }
    public void setDirty() { if (status==S_PIXKEPOK) status=S_PIXOK; }
    public String getFilenev() { return filenev; }
    // public void setKep(Image kep_) { kep=kep_; }
    // public int setStatus(int status) { return this.status=status; } // Kepbetolto allithatja

    public void ujraszamol() {}
    /**
     * @return új this.status
     */
    public int betolt_probal() {
      if (status!=S_JONMEG) return status;
      width=kep.getWidth(null);
      if (width==-1) return status;
      height=kep.getHeight(null);
      if (height<1) throw new Kepbetolto.SikertelenBetoltesException();
      if (width<1) throw new Kepbetolto.SikertelenBetoltesException();
      status=S_KEPOK;
      ujraszamol();
      return status;
    }
    protected void betolt() {
      if (S_JONMEG==betolt_probal()) throw new Kepbetolto.SikertelenBetoltesException();
    }
    public void betoltve() {
      /* akkor hivódik meg, amikor a Képbetöltő végzett */
      betolt();
    }

    public void pixelizal() {
      /* lekérdezi (frissíti) a pixels tömböt kep alapján */
      betolt();
      if (0!=(status&S_PIXOK)) return;
      if (status==S_JONMEG) throw new MegToltodikException();
      Image masik;
      if (co!=null && OkosAWT.getMainToolkit().getColorModel().getPixelSize()==16) {
        // !!!
        // ezt miért is kell külön kezelni?
        masik=OkosAWT.getMainComponent().createImage(width, height);
        Graphics g=masik.getGraphics();
        g.drawImage(kep, 0, 0, null);
        g.dispose();
      } else masik=kep;
      pixels=new int[width*height];
      PixelGrabber pg=new PixelGrabber(kep.getSource(), 0, 0, width, height, pixels, 0, width);
      try {
        if (!pg.grabPixels(0L)) pg.grabPixels(0L);
        if ((pg.status() & 0x80) != 0) throw new MegToltodikException();
      } catch (Exception e) { throw new MegToltodikException()}
      if (masik!=kep) masik.flush()// ??
      status|=S_PIXOK;
    }

    public void kepizal() {
      /* újragenerálja kep-et pixels alapján */
      betolt();
      if (0!=(status&S_KEPOK)) return;
      boolean van_atlat=false;
      for (int i=pixels.length-1;i>=0;i++) if (0xff000000!=(pixels[i]&0xff000000)) { van_atlat=truebreak}
      if (van_atlat) { // transparent-et másképp készítünk, mint simát
        kep=OkosAWT.getMainComponent().createImage(new MemoryImageSource(width, height, pixels, 0, width));
      } else {
        // Imp: kézzel, ciklusban felrajzolni a pixeleket
        kep=OkosAWT.getMainComponent().createImage(new MemoryImageSource(width, height, pixels, 0, width));
      }
      status|=S_KEPOK;
    }

    public int[] getPixelt() { pixelizal()return pixels; }
    /**
     * a sorokból álló tömböt adja vissza, transparency-t nem törli le
     */
    public int[][] getPixeltt() {
      int x, y, t[][], u[], i=0;
      pixelizal();
      t=new int[height][];
      for (y=0;y<height;y++) {
        u=t[y]=new int[width];
        for (x=0;x<width;x++) u[x]=pixels[i++];
      }
      return t;
    }
    /**
     * a sorokból álló tömböt adja vissza, transparency-t letörli
     */
    public int[][] getPixelttnot() {
      int x, y, t[][], u[], i=0;
      pixelizal();
      t=new int[height][];
      for (y=0;y<height;y++) {
        u=t[y]=new int[width];
        for (x=0;x<width;x++) u[x]=pixels[i++]&0xFFFFFF;
      }
      return t;
    }
    /**
     * A sorokból álló tömböt adja vissza, transparency-t letörli, az egyes
     * színkomponenseket 00-ra vagy 0xff-re kerekíti.
     */
    public int[][] getPixelttkerek() {
      int x, y, t[][], u[], i=0, p;
      pixelizal();
      t=new int[height][];
      for (y=0;y<height;y++) {
        u=t[y]=new int[width];
        for (x=0;x<width;x++) {
          p=pixels[i++]&0xFFFFFF;
          if ((p&0xFF0000)>=0x700000) p|=0xFF0000; else p&=~0xFF0000;
          if ((p&0x00FF00)>=0x007000) p|=0x00FF00; else p&=~0x00FF00;
          if ((p&0x0000FF)>=0x000070) p|=0x0000FF; else p&=~0x0000FF;
          u[x]=p;
        }
      }
      return t;
    }
    public void pixelt_felejt() {
      if (0!=(status&S_PIXOK)) {
        pixels=null;
        status&=~S_PIXOK;
      }
    }
    public void kepet_felejt() {
      status&=~S_KEPOK;
    }
    public Object clone() {
      // még le nem töltődött kép nem klónozható értelmesen
      kepizal();
      ButaKep masik=new ButaKep(filenev, kep, co);
      masik.width=width;
      masik.height=height;
      return masik;
    }

    public void atszinez(int ujszin) {
      /* az összes nem átlátszó képpont színét ujszin-re állítja */
      ujszin|=0xFF000000;
      int t[]=getPixelt();
      kepet_felejt();
      for (int i=t.length-1;i>=0;i--) if (0!=(t[i]&0xFF000000)) t[i]=ujszin;
      kepizal();
    }
  } /// class ButaKep

  public static class Betutipus {
    /* a Betűtípus annyival több a ButaKép-nél, hogy önmagán belül 256
     * karakterpozíciót (méretekkel együtt) képes megjegyezni. A betűk alakját
     * egy speciális képfile-ból (.gif) töltjük be, és a file-ban tárolt kék
     * és piros vonalak elhelyezkedéséből automatikusan számítjuk a méreteket
     * A betűk mind ugyanolyan színűek, az átszínezéshez új :Betűtípus szükséges.
     */
    // by pts@fazekas.hu at Sun Mar 18 01:28:16 CET 2001
    public static class HibasBetutipusException extends Error {}
    public static class TulSokBetuException extends HibasBetutipusException {}
    public static class TulKevesBetuException extends HibasBetutipusException {}
    public static final int B_KEK=ButaKep.C_KEK, B_PIROS=ButaKep.C_PIROS;
    // ^^^ átlátszóság nem számít

    private static class Betu {
      protected int x, y; // bal felső pixel pozíciója a képen
      protected int gw, gh; // mekkora területet kell kirajzolni
      protected int w; // hány pixellel kell jobbra menni a kirajzolás után
      protected int bl; // a baseline hányadik sorban van (felülről, >=0)
      public Betu(int x, int y) { this.x=x; this.y=y; }
    }
    ButaKep kep; // átlátszó + 1 szín
    Betu betuk[];

    public static char javit(char c) {
      /* kicserél néhány magyar ékezetes betűt (lényegében Unicode->Latin 2) */
      return c==0x150 ? 0xD5 :
             c==0x151 ? 0xF5 :
             c==0x170 ? 0xDB :
             c==0x171 ? 0xFB :
             c;
    }
    public static String javit(String s) {
      /* karakterenként javít */
      StringBuffer sb=new StringBuffer();
      for (int i=0;i<s.length();i++) sb.append(javit(s.charAt(i)));
      return new String(sb);
    }

    public int nyomtat(Graphics g, int x, int y, char c) {
      /* (x,y): reference point (a baseline-on)
       * c: nyomtatandó karakter
       * @return: új x koordináta, miután jobbra léptünk
       */
      Betu b=betuk[c];
      Graphics g1=g.create(x, y-b.bl, b.gw, b.gh);
      g1.drawImage(kep.getImage(), -b.x, -b.y, null);
      g1.dispose();
      return x+b.w;
    }
    public int nyomtatBalra(Graphics g, int x, int y, char c) {
      /* (x,y): reference point (a baseline-on)
       * c: nyomtatandó karakter
       * @return: új x koordináta, miután jobbra léptünk
       */
      Betu b=betuk[c];
      Graphics g1=g.create(x-b.w, y-b.bl, b.gw, b.gh);
      g1.drawImage(kep.getImage(), -b.x, -b.y, null);
      g1.dispose();
      return x-b.w;
    }

    public int nyomtat(Graphics g, int x, int y, String s) {
      /* (x,y): reference point (a baseline-on)
       * s: nyomtatandó String
       * @return: új x koordináta, miután jobbra léptünk
       */
      // Imp: gyorsítani, de mindig jó g.create()
      for (int i=0;i<s.length();i++)
        x=nyomtat(g, x, y, s.charAt(i));

      // g.drawImage(kep.getImage(), 0, 0, null);
      /*System.out.println(betuk[65].x+","+betuk[65].y+" w="+betuk[65].gw+" h="+betuk[65].gh);
      System.out.println(betuk[65+16].x+","+betuk[65+16].y);
      System.out.println("");
      */
      return x;
    } /// nyomtat()

    public int nyomtatBalra(Graphics g, int x, int y, String s) {
      /* (x,y): reference point (a baseline-on)
       * s: nyomtatandó String
       * @return: új x koordináta, miután jobbra léptünk
       */
      // Imp: gyorsítani, de mindig jó g.create()
      for (int i=s.length()-1;i>=0;i--)
        x=nyomtatBalra(g, x, y, s.charAt(i));

      // g.drawImage(kep.getImage(), 0, 0, null);
      /*System.out.println(betuk[65].x+","+betuk[65].y+" w="+betuk[65].gw+" h="+betuk[65].gh);
      System.out.println(betuk[65+16].x+","+betuk[65+16].y);
      System.out.println("");
      */
      return x;
    } // nyomtatBalra()

    public Betutipus(ButaKep kep) {
      kep.betolt();
      this.kep=kep;
      betuk_szamol();
    } /// Betutipus()

    public Betutipus(ButaKep kep, int szin) {
      this(kep);
      kep.atszinez(szin);
    }

    protected void betuk_szamol() {
      /* felépítjük a betűk[] tömböt a `kép' alapján */
      int t[][]=kep.getPixelttnot();
      int u[];
      int i=0;
      betuk=new Betu[256];
      for (int y=1;y<t.length-1;y++) {
        u=t[y];
        for (int x=2;x<u.length-1;x++) {
          if (u[x]==B_KEK && u[x+1]==B_KEK && t[y-1][x]==B_KEK) {
            if (i==betuk.length) throw new TulSokBetuException();
            int xx, yy, yybl=-1, ww=x+10;
            for (xx=x;xx<u.length-1;xx++) {
              if (u[xx]!=B_KEK) break;
              if (t[y+1][xx]==B_PIROS) ww=xx;
            }
            for (yy=y;yy>0;yy--) {
              if (t[yy][x-1]==B_PIROS) yybl=yy;
              if (t[yy][x]!=B_KEK) break;
            }
            if (yybl==-1) throw new HibasBetutipusException();
            betuk[i]=new Betu(x+1,yy+1);
            betuk[i].gw=xx-x-1;
            betuk[i].gh=y-yy-1;
            betuk[i].bl=yy-yybl;
            betuk[i].w=ww-x; // !!!
            i++;
          } /// IF betűt találtunk
        } /// NEXT x
      } /// NEXT y
      if (i!=betuk.length) throw new TulKevesBetuException();
    } /// betuk_szamol()
  } /// class Betutipus

  public static class Kep extends ButaKep {
    /* a Kép annyival több a ButaKép-nél, hogy van bx1, by1, és képes
     * ütközésvizsgálatra, továbbá megjeleníthető sprite-ként
     */
    protected int bx1, by1; // hany pixellel kell a bal felso sarkot feljebb kirajzolni
    private int bxa, bya; // bxa==bx1-arnyeka, bya==by1-arnyeka
    private int widtha=-1, heighta=-1; // widtha==width+2*arnyeka, ...
    protected boolean arnyekolt;
    protected int arnyeka=5;
    protected Sdpts sd;

    private Hashtable utkoztetok=new Hashtable()// Kep->Utkozteto

    public Kep(String filenev, Image kep) { super(filenev,kep,null)}
    public Kep initi(Sdpts sd, int bx1, int by1, int arnyeka) {
      this.co=sd.getComponent();
      this.sd=sd; this.bx1=bx1; this.by1=by1;
      this.arnyekolt=truethis.arnyeka=arnyeka==-9999?5:arnyeka;
      return this;
    }
    public Kep initi(Sdpts sd, int bx1, int by1) {
      this.co=sd.getComponent();
      this.sd=sd; this.bx1=bx1; this.by1=by1;
      this.arnyekolt=falsethis.arnyeka=0;
      return this;
    }

    public int getBx1() { return bx1; }
    public int getBy1() { return by1; }

    public boolean getArnyekolt() { return arnyekolt; }
    public void setArnyekolt(boolean arnyekolt, int arnyeka) {
      this.arnyekolt=arnyekolt;
      this.arnyeka=arnyeka;
      ujraszamol();
    }

    public void ujraszamol() {
      /* bxa, bya, widtha, heighta újraszámolása */
      heighta=height+2*arnyeka;
      widtha=width+2*arnyeka;
      bxa=bx1-arnyeka;
      bya=by1-arnyeka;
    }

    public void rarajzol(Graphics g, int x, int y) {
      // (x,y): pixel
      // java.lang.System.out.println("RR.\n");
      g.drawImage(kep, x+bx1, y+by1, null);
    }
    public void felrajzol_arnyek(int x, int y) {
      // (x,y): pixel
      if (arnyekolt) sd.drawShadow(x+bxa, y+bya, widtha, heighta);
    }
    public void felrajzol_kep(int x, int y) {
      // (x,y): pixel
      if (arnyekolt) {
        sd.drawImage(kep, x+bx1, y+by1);
        sd.drawCover(x+bxa, y+bya, widtha, heighta);
      } else sd.drawImageCover(kep, x+bx1, y+by1, !arnyekolt);
    }

    public void utkoztetot_hozzaad(Kep masik) {
      if (!      utkoztetok.containsKey(masik)) utkoztetok.put(masik, new Utkozteto(this,masik));
      if (!masik.utkoztetok.containsKey(this )) masik.utkoztetok.put(this,  new Utkozteto(masik,this));
    }
    public boolean utkozik_e(Kep masik, int dx, int dy) {
      // (dx,dy): koordináta-különbség, pixelben, nem bal-felső koordináta, hanem (bxa,bya)-val eltolt
      Utkozteto u=(Utkozteto)utkoztetok.get(masik);
      if (u==null) { utkoztetot_hozzaad(masik); u=(Utkozteto)utkoztetok.get(masik)}
      // u: k1==this, k2==masik
      return u.utkoznek_e(dx, dy);
    }
  }

  public static class Utkozteto {
    /* Az Ütköztető két Kép közötti ütközési táblát tárol. k1: (x1,y1)=(0,0). */
    // 2000.03.15
    // Ez most jó.
    // Imp: test with different sizes
    public static class TablaFelismeresException extends Error {}
    Kep k1, k2;
    int alapy;
    int bt[], jt[];
    public boolean utkoznek_e(int dx, int dy) {
      // (dx,dy): koordináta-különbség, pixelben, nem bal-felső koordináta, hanem (bxa,bya)-val eltolt
      // dx: k2.x-k1.x
      try {
        return dx>bt[dy+alapy] && dx<jt[dy+alapy];
      } catch (ArrayIndexOutOfBoundsException e) {
        return false;
      }
    }
    public void dump_teszt() {
      System.out.println("alapy="+alapy+".\n");
      for (int i=0;i<bt.length;i++) System.out.println("  bt["+i+"]="+bt[i]+" jt[i]="+jt[i]+".");
      System.out.println("utk.veg.");
    }

    private static int kivon_min(int defa, int i, int j) {
      return (i==-1 || j==-1)?defa:Math.min(defa,i-j);
    }
    public Utkozteto(Kep k1, Kep k2) {
      /* felépítjük az ütközési táblákat stb. Lényegében felrajzoljuk a két
       * képet egymás közelében az összes lehetséges módon, majd megnézzük,
       * hogy ütköztek-e.
       * Ez időigényes.
       */
      int xx, x, y, u[];

      // k1 számítása
      this.k1=k1;
      int t1[][]=k1.getPixeltt();
      int h1=k1.getHeight();
      int b1[]=new int[h1];
      int j1[]=new int[h1];
      int w1=k1.getWidth();
      for (y=h1-1;y>=0;y--) {
        u=t1[y];
        for (x=0;x<w1;x++) if (0!=(u[x]&0xFF000000)) break;
        // Most: x: a legbaloldalibb, nem átlátszó képpont a sorban (0<=x<w1)
        if (x==w1) { b1[y]=j1[y]=-1; continue}
        b1[y]=x;
        for (xx=w1-1;xx>x;xx--) if (0!=(u[xx]&0xFF000000)) break;
        xx++;
        // Most: xx: a legjobboldalibb, nem átlátszó képpont a sorban (0<=xx<w1)
        j1[y]=xx;
        //System.out.println(xx);
      }

      /*for (y=0;y<h1;y++) {
        for (x=0;x<w1;x++) System.out.print(-(t1[y][x]>>31)+" ");
        System.out.println("");
      }*/


      // k2 számítása
      this.k2=k2;
      int t2[][]=k2.getPixeltt();
      int h2=k2.getHeight();
      int b2[]=new int[h2];
      int j2[]=new int[h2];
      int w2=k2.getWidth();
      for (y=h2-1;y>=0;y--) {
        u=t2[y];
        for (x=0;x<w2;x++) if (0!=(u[x]&0xFF000000)) break;
        // Most: x: a legbaloldalibb, nem átlátszó képpont a sorban (0<=x<w2)
        if (x==w2) { b2[y]=j2[y]=-1; continue}
        b2[y]=x;
        for (xx=w2-1;xx>x;xx--) if (0!=(u[xx]&0xFF000000)) break;
        xx++;
        // Most: xx: a legjobboldalibb, nem átlátszó képpont a sorban (0<=xx<w2)
        j2[y]=xx;
      }

      // közös számítás
      int dbx=k2.getBx1()-k1.getBx1();
      alapy=h2-1+k2.getBy1()-k1.getBy1();
      bt=new int[h1+h2-1];
      jt=new int[h1+h2-1];
      /*for (y=-h2+1;y<=h1-1;y++) {
        // bt[y+h2-1]-et és jt[y+h2-1]-et töltjük ki
        int y1_mar_nem=Math.min(h1,y+h2);
        for (int y1=0;y1<y1_mar_nem;y1++) {
          // k1-nek y1. sorát, k2-nek y+h2-1. sorát vizsgáljuk
        }
      }*/
      for (y=0;y<=h1+h2-2;y++) {
        // bt[y]-t és jt[y]-t töltjük ki
        int y1_indul=Math.max(0,y-h2+1);
        int y1_mar_nem=Math.min(h1,y+1);
        bt[y]=jt[y]=Integer.MAX_VALUE;
        // System.out.println("y="+y+" y1="+y1_indul+".."+(y1_mar_nem-1)+" y2="+(y1_indul-y+h2-1)+".."+(y1_mar_nem-y+h2-1-1));
        for (int y1=y1_indul;y1<y1_mar_nem;y1++) {
          // k1-nek y1. sorát vizsgáljuk

          //bt[y]=Math.min(bt[y],b1[y1]-j2[0]);
          //jt[y]=Math.max(jt[y],b2[0]-j1[y1]);

          //bt[y]=Math.min(bt[y],-b1[y1]+j2[y1-y+h2-1]);
          //jt[y]=Math.max(jt[y],b2[y1-y+h2-1]-j1[y1]);

          //bt[y]=Math.min(bt[y],kivon(bt[y],b1[y1],j2[y1-y+h2-1]));
          //jt[y]=Math.max(jt[y],kivon(jt[y],j1[y1],b2[y1-y+h2-1]));

          bt[y]=kivon_min(bt[y],b1[y1],j2[y1-y+h2-1]);
          jt[y]=kivon_min(jt[y],b2[y1-y+h2-1],j1[y1]);

          //System.out.print("  by1="+y1+": "+(b1[y1])+" ;; "+(j2[y1-y+h2-1]));
          //System.out.println("  y1="+y1+": "+(b2[y1-y+h2-1])+" ;; "+(j1[y1]));
        }
        if ((bt[y]==Integer.MAX_VALUE || jt[y]==Integer.MAX_VALUE) && bt[y]!=jt[y])
          throw new TablaFelismeresException()// belső hiba, soha nem következhet be
        if (bt[y]==Integer.MAX_VALUE) jt[y]=bt[y]=0;
                                 else { bt[y]+=dbx; jt[y]=dbx-jt[y]}
      }
      // már kész is vagyunk :-)
    } ///Utkozteto()
  } // class Utkozteto




  public static class Sdpts {
    /*
    Image createEmptyTransparentImage(int w, int h) {
      // by pts at 2001.03.10
      // toolkit.createImage??
      // images created this way are read-only, so getGraphics() would fail on this image
      Image kep=a.createImage(new MemoryImageSource(w, h, new int[w*h], 0, width));
      a.prepareImage(kep, -1, -1, null);
      return kep;
    }
    */


      private Component a;
      private FontMetrics fm;

      private int width;
      private int height;
      private Image surface;
      // surface mérete: (width x 2*height); két részből áll:
      //   felseje: előtér--rajztér, (0,0), width x height
      //   alsaja: háttér, (0,height), width x height
      // width x height általában a teljes Applet téglalap mérete, ami
      //   PacMan-nél width==224, height==288
      //   Kisgömböc-nél width==640, height==480
      // egy width x height méretű téglalap kirajzolása a képernyőre Java-ban
      //   viszonylag lassú művelet (főleg akkor, ha másodpercente >30-szor meg
      //   akarjuk tenni), a fő célunk az, hogy minél többet megspóroljunk egy
      //   ilyen kirajzolásból. Tehát mindig csak azokat a részeket frissítjük,
      //   amelyek az utolsó kirajzolás óta módosultak. Azzal nem törődünk, hogy
      //   egy rész többször is módosult (ekkor -- pazarlóan -- többször
      //   frissítjük).
      // Ez a class végzi ezt a frissítés--cache-elést, meg miegymást.
      // Megjegyzés: az eredeti változat `Applet a;'-t deklarált,
      //   de nekünk elég `java.awt.Component a' is.
      // Hogyan használjuk?
      //   1. a class double buffering-et (offscreen rajzolást) valósít meg.
      //   2. az offscreen buffer a surface felseje.
      //   3. a surface alsaját sosem rajzoljuk ki közvetlenül, kizárólag a
      //      felsejébe másoljuk (esetleg csak egyes részeit), és majdan a
      //      felsejét rajzoljuk ki
      //   4. a surface alsajában érdemes a hátteret tárolni, tehát azt az
      //      alapképet, ami többé-kevésbé állandó, csak ritkán kell frissíteni.
      //      Ilyen például PacMan játék esetén a pálya (labirintus) és az
      //      életszám-kijelző.
      // Execution:
      //   Sdpts sd=new Sdpts(#, #, Applet.this);
      //   ...
      //   Graphics g=sd.getBackgroundGraphics();
      //     ... // g-re rajzolgatunk -> alsaja
      //   sd.dirtyAdd(#,#,#,#); // amit átírtunk
      //   ...
      //   sd.dirtyClear(); // alsaja -> felseje
      //     sd.draw*(...); // -> felseje
      //     ...
      //   // Applet.this.paint(g)-ben:
      //     sd.render(g,0,0); // (csak a megváltozott bbox-ot) vagy...
      //     sd.renderAll(g,0,0); // egész felsejét kirajzolja
      // .

    private Image cover, shadow;

    private Graphics surfaceGC;  // surface teljes egészére
    private Graphics surfaceGC1; // surface felsejére
    private Graphics surfaceGC2; // surface alsajára
    //private Graphics surfaceGC3; // surface fedőlapja
    //private Graphics surfaceGC4; // surface árnyéklapja

    private int dirtyX1;
    private int dirtyY1;
    private int dirtyX2;
    private int dirtyY2;
    private int oldDirtyX1;
    private int oldDirtyY1;
    private int oldDirtyX2;
    private int oldDirtyY2;
    private Rectangle dirtyRect[];
    private int nDirty;

    public Sdpts(int w, int h, Component applet) {
      dirtyRect = new Rectangle[200];
      width = w; height = h;
      a = applet;
      // surface = a.createImage(width, height * 2);
      surface = OkosAWT.getMainComponent().createImage(width, height * 2);
      if (surface==null) throw new NullPointerException();
      // ^^^ Aaargh. Lüke Java nem tud képet készíteni ha a komponens még nem látszik.

          //surface=java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment().
          //  getDefaultScreenDevice().getDefaultConfiguration().
          //  createCompatibleImage(width,2*height,
          //  java.awt.Transparency.OPAQUE);
          // ^^^ BITMASK-kal nagyon elrondul, de opaque-kal olyan, mint a sima
          //     createImage

          //cover=java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment().
          //  getDefaultScreenDevice().getDefaultConfiguration().
          //  createCompatibleImage(width,height,
          //  java.awt.Transparency.BITMASK);
          // ^^^ explorer-ben SecurityViolation :-((

          // !!!
          /*
          // kempelen JDK1.2.2 és MSIE OK
          try {
            cover=((java.awt.Graphics2D)applet.getGraphics()).getDeviceConfiguration().
              createCompatibleImage(width,height*2,
              java.awt.Transparency.BITMASK);
            // ^^^ cover.getType()==0 (SUN)
          } catch (ClassCastException ce) {
            cover=a.createImage(1,1); // not smaller...
          }*/

          /* cover=createEmptyTransparentImage(width, height*2); */

          // cover=(BufferedImage)a.createImage(width,height);
          // ^^^^ cover.getType()==5; no BufferedImage at all in MS!!

          // cover=new BufferedImage(width, height, alfasit(((BufferedImage)surface).getType()));
          // ^^^ ez siman meghal internet exploderben, hianyzik neki egy belso metodus
          // ^^^ cover.getType()==7; erezhetoen lassabb appletviewerben

          // System.out.println(cover.getType());
          // System.out.println(a.prepareImage(cover, -1, -1, a)); -- always true

          surfaceGC = surface.getGraphics();
          surfaceGC1 = surfaceGC.create(0, 0, width, height);
          surfaceGC2 = surfaceGC.create(0, height, width, height);
          // surfaceGC3 = surfaceGC.create(0, 2*height, width, height);
          //surfaceGC3 = cover.getGraphics().create(0, 0,      width, height);
          //surfaceGC4 = cover.getGraphics().create(0, height, width, height);
          setFont("", 1, 14);
          for(int k = 0; k < dirtyRect.length; k++)
              dirtyRect[k] = new Rectangle();
          surfaceGC.setColor(Color.black);
          surfaceGC.fillRect(0, 0, width, height * 2);
    }

  public Image createImage(int w, int h) {
    // by pts at 2001.03.10
    // creates r/w images
    return a.createImage(w, h);
  }
  public Image createImage(ImageProducer ip) {
    // by pts at 2001.03.10
    // toolkit.createImage??
    // images created this way are read-only, so getGraphics() would fail on this image
    Image kep=a.createImage(ip);
    a.prepareImage(kep, -1, -1, null);
    return kep;
  }

  public Component getComponent() { return a; }
  public int getWidth() { return width; }
  public int getHeight() { return height; }

      public void invalidate(int i, int j, int k, int l) {
          // k: width, l: height
          int i1 = i + k;
          int j1 = j + l;
          if(i < dirtyX1)
              dirtyX1 = i;
          if(i1 > dirtyX2)
              dirtyX2 = i1;
          if(j < dirtyY1)
              dirtyY1 = j;
          if(j1 > dirtyY2)
              dirtyY2 = j1;
      }

      public void dirtyAdd(int i, int j, int k, int l)
      {
          if(nDirty == dirtyRect.length)
          {
              Rectangle arectangle[] = new Rectangle[dirtyRect.length * 2];
              System.arraycopy(dirtyRect, 0, arectangle, 0, dirtyRect.length);
              for(int j1 = dirtyRect.length; j1 < arectangle.length; j1++)
                  arectangle[j1] = new Rectangle();

              dirtyRect = arectangle;
          }
          int i1 = i;
          int k1 = j;
          int l1 = k + i;
          int i2 = l + j;
          if(i1 < 0) i1 = 0;
          if(k1 < 0) k1 = 0;
          if(l1 < 0) l1 = 0;
          if(i2 < 0) i2 = 0;
          if(i1 > width)  i1 = width;
          if(k1 > height) k1 = height;
          if(l1 > width)  l1 = width;
          if(i2 > height) i2 = height;
          Rectangle rectangle = dirtyRect[nDirty++];
          rectangle.x = i1;
          rectangle.y = k1;
          rectangle.width = l1 - i1;
          rectangle.height = i2 - k1;
          if(i1 < dirtyX1) dirtyX1 = i1;
          if(l1 > dirtyX2) dirtyX2 = l1;
          if(k1 < dirtyY1) dirtyY1 = k1;
          if(i2 > dirtyY2) dirtyY2 = i2;
      }

      public void fullCover() {
        // surface fedőlapját a felsejére teríti
        if (cover!=null) surfaceGC.drawImage(cover, 0, 0, a);
        // ^^^ gyors, elegáns szép (pts, 2001.03.04) :-))
      }

      public void dirtyCover() {
          // surface fedőlapjáról a felsejébe másolja a felsejében megváltozott részeket
          int i = height*2;
          // if (false)
          for(int j = 0; j < nDirty; j++) {
              Rectangle rectangle = dirtyRect[j];

              //surfaceGC.drawImage(((java.awt.image.BufferedImage)cover).getSubimage(rectangle.x, rectangle.y, rectangle.width, rectangle.height),
              //  rectangle.x, rectangle.y, a);
              // ^^^ tök jó, de kicsit lassú

              Graphics g1=surfaceGC.create(rectangle.x, rectangle.y, rectangle.width, rectangle.height);
              if (cover!=null) g1.drawImage(cover, -rectangle.x, -rectangle.y, a);
              // ^^^ gyors, elegáns szép (pts, 2001.03.04) :-))
              g1.dispose();

              // surfaceGC.copyArea(rectangle.x, rectangle.y + i, rectangle.width, rectangle.height, 0, -i);
              //surfaceGC.drawImage(cover, rectangle.x, rectangle.y, rectangle.width, rectangle.height,
              //  rectangle.x, rectangle.y, rectangle.width, rectangle.height,
              //  a);
              //Graphics g1=surfaceGC.create(rectangle.x, rectangle.y, rectangle.width, rectangle.height);
              ////g1.drawImage(cover, 0, 0, a);
              //g1.setColor(Color.white);
              //g1.fillRect(0,0, rectangle.width, rectangle.height); // ez pont jó
          }
      }

  public void dirtyAddClear() {
    dirtyAdd(0,0,width,height);
    dirtyClear();
  }

  public void dirtyAddMax() {
    dirtyAdd(0,0,width,height);
  }

  /**
   * Surface alsajából a felsejébe (!) másolja (az előző körben) megváltozott
   * részeket.
   */
  public void dirtyClear() {
      int i = height;
      for(int j = 0; j < nDirty; j++) {
          Rectangle rectangle = dirtyRect[j];
          surfaceGC.copyArea(rectangle.x, rectangle.y + i, rectangle.width, rectangle.height, 0, -i);
      }

      oldDirtyX1 = dirtyX1;
      oldDirtyX2 = dirtyX2;
      oldDirtyY1 = dirtyY1;
      oldDirtyY2 = dirtyY2;
      nDirty = 0;
      dirtyX1 = 0x7fffffff;
      dirtyX2 = 0x80000000;
      dirtyY1 = 0x7fffffff;
      dirtyY2 = 0x80000000;
  }


      public void renderAll(Graphics g, int gx, int gy) {
        // g:(gx,gy)-ra kirajzolja surface teljes felsejét
        // általában gx==0 && gy==0
        Graphics g1 = g.create(gx, gy, width, height);
        g1.drawImage(surface, 0, 0, null);
        g1.dispose()// memory leak discovered by pts!!
      }

      public void renderAll(Graphics g, int gx, int gy, int k, int l) {
          // g:(gx,gy)-(k,l)-re kirajzolja surface teljes felsejét
          g.drawImage(surface, gx, gy, k, l, 0, 0, width, height, null);
      }


      public void render(Graphics g, int i, int j) {
        // g:(i,j)-re kirajzolja surface felsejének a legutóbbi dirtyClear()
        // óta megváltozott részeit
        int k = Math.min(oldDirtyX1, dirtyX1);
        int l = Math.min(oldDirtyY1, dirtyY1);
        int i1 = Math.max(oldDirtyX2, dirtyX2);
        int j1 = Math.max(oldDirtyY2, dirtyY2);
        if (nDirty == 0 && (k == 0x7fffffff || l == 0x7fffffff || i1 == 0x80000000 || j1 == 0x80000000)) return;
        Graphics g1 = g.create(k + i, l + j, i1 - k, j1 - l);
        g1.drawImage(surface, -k, -l, null);
        g1.dispose()// memory leak discovered by pts!!
      }

      public void burn() {
          // surface felsejét az alsajába másolja
          surfaceGC2.drawImage(surface, 0, 0, a);
      }

      public void burn(int i, int j, int k, int l) {
          // surface felsejének egy részét az alsajába másolja
          surfaceGC.copyArea(i, j, k, l, 0, height);
      }


      public Image getBackground() {
          // surface alsaját adja vissza új képként
          Image image = a.createImage(width, height);
          image.getGraphics().drawImage(surface, 0, -height, a);
          return image;
      }

      public void setColor(Color color)
      {
          surfaceGC1.setColor(color);
      }

      public void setFont(String s, int i, int j)
      {
          setFont(new Font(s, i, j));
      }

      public void setFont(Font font)
      {
          surfaceGC1.setFont(font);
          fm = surfaceGC1.getFontMetrics();
      }

    /**
     * Felseje grafikus kontextusa.
     */
    public Graphics getGraphics() {
        return surfaceGC1;
    }

    public FontMetrics getFontMetrics() {
        return surfaceGC1.getFontMetrics();
    }

    /**
     * Alsaja grafikus kontextusa.
     */
    public Graphics getBackgroundGraphics() {
        return surfaceGC2;
    }

    public void setCover(Image cover) { this.cover=cover; if (cover!=null) a.prepareImage(cover, a)}
    public void setShadow(Image shadow) { this.shadow=shadow; if (shadow!=null) a.prepareImage(shadow, a)}


    /*public Graphics getCoverGraphics() {
          // extension by pts
          return surfaceGC3;
      }
      public Graphics getShadowGraphics() {
          // extension by pts
          return surfaceGC4;
      }*/

      public void setBackground(Color color) {
          // surface alsaját kitölti color színnel
          surfaceGC2.setColor(color);
          surfaceGC2.fillRect(0, 0, width, height);
          dirtyAdd(0, 0, width, height);
      }

      public void setBackground(Image image, boolean flag) {
          // surface alsajára rárajzolja a megadott (háttér)képet
          surfaceGC2.drawImage(image, 0, 0, a);
          if(flag)
              dirtyAdd(0, 0, width, height);
      }



      public void fillRect(int i, int j, int k, int l) {
          // surface felsejére rajzol...
          surfaceGC1.fillRect(i, j, k, l);
          dirtyAdd(i, j, k, l);
      }


      public void drawString(String s, int i, int j) {
          // surface felsejére rajzol...
          int k = fm.stringWidth(s);
          surfaceGC1.drawString(s, i, j + fm.getAscent());
          dirtyAdd(i, j, k, fm.getHeight());
      }

      public void drawPoint(int i, int j) {
          // surface felsejére rajzol...
          surfaceGC1.drawLine(i, j, i, j);
          dirtyAdd(i, j, 1, 1);
      }

      public void drawPoint(int i, int j, Color color) {
          // surface felsejére rajzol...
          surfaceGC1.setColor(color);
          surfaceGC1.drawLine(i, j, i, j);
          dirtyAdd(i, j, 1, 1);
      }

      public void drawImage(Image image, int i, int j) {
          // surface felsejére rajzol...
          surfaceGC1.drawImage(image, i, j, a);
          dirtyAdd(i, j, image.getWidth(a), image.getHeight(a));
      }

      public void drawImageCover(Image image, int x, int y, boolean dirty) {
          // surface felsejére rajzol, de felülírja előtakaróval
          int w=image.getWidth(a);
          int h=image.getHeight(a);
          Graphics g1=surfaceGC.create(x, y, w, h);
          g1.drawImage(image, 0, 0, a);
          if (cover!=null) g1.drawImage(cover, -x, -y, a);
          // ^^^ gyors, elegáns szép (pts, 2001.03.04) :-))
          g1.dispose();
          // if (dirty)
          dirtyAdd(x, y, w, h);
      }
      public void drawShadow(int x, int y, int w, int h) {
        // surface felsejére rajzol árnyékot
        Graphics g1=surfaceGC.create(x, y, w, h);
        // if (shadow!=null) g1.drawImage(shadow, -x, -height-y, a);
        if (shadow!=null) g1.drawImage(shadow, -x, -y, a);
        // System.out.println("shadowed");
        g1.dispose();
        dirtyAdd(x, y, w, h)// -- no speedup :-((
      }
      public void drawCover(int x, int y, int w, int h) {
        // surface felsejére rajzol árnyékot
        Graphics g1=surfaceGC.create(x, y, w, h);
        if (cover!=null) g1.drawImage(cover, -x, -y, a);
        g1.dispose();
        // dirtyAdd(x, y, w, h); // speedup
      }
  } // class Sdpts

  /**
   * Olyan komponens, ami egyetlen Sdpts-t jelenít meg.
   */
  public static class SdRajzolo extends OkosFixCanvas {
    protected Sdpts sd;
    /**
     * Alapesetben kirajzolja az egesz teglalapot hatterszinnel teliti meg,
     * majd meghivja a paint-et. Azert rossz megis az alapeset, mert a
     * bongeszo csak a paint-et hivja, mig a repaint() az update()-et. Szóval
     * biztos ami biztos, mi rögtön a paint()-et hívjuk.
     */
    public void update(Graphics g) { this.paint(g)}
    public void paint(Graphics g) { // Canvas újrarajzolás
      sd.renderAll(g, 0, 0);
    }
    /**
     * Olyan mint a repaint(), de szinkron módon fut, és nem esik áldozatul
     * a JDK elrontott frissítés-optimalizálásának.
     */
    public void paintMost() {
      Graphics g=this.getGraphics();
      if (g!=null) {
        synchronized(this) {
          paint(g)// valószínűleg át van definiálva
        }
        g.dispose();
      }
    }
    protected void init_sd() {
      this.sd=new Sdpts(this.getSize().width, this.getSize().height, this);
    }
    public Sdpts getSd() { return sd; }
  } /// class Sdrajzolo
} // class Kepek