
import java.awt.*;
import java.awt.Color;
import java.awt.geom.*;
import java.awt.image.*;
import java.io.*;
import java.net.*;
import java.lang.*;
import java.util.*;
import javax.imageio.*;
import javax.swing.*;
import se.datadosen.jalbum.JAFilter;
import se.datadosen.jalbum.ModifiesSize;
import se.datadosen.util.*;

                                                                   /**
 * Title:        JAlbum filter to add a variety of borders to images.
 * Copyright:    Copyright (c) 2004-2005
 * @author       Jens Troeger. Slightly modified by David Ekholm
 * @version      0.96
 */


public class XBorderFilter implements JAFilter, ModifiesSize
{

  // Parameterless constructor
  public XBorderFilter()
  {
    System.out.println(versionString);
    invalidateCache();
  }

  // Implements JAFilter
  public String getName()
  {
    return "eXtended Border filter";
  }

  // Implements JAFilter
  public String getDescription()
  {
    return "Add a variety of borders to images";
  }

  // Implements ModifiesSize
  public Dimension getModifiedSize(Dimension originalSize, Map vars)
  {
    // reinterpret params
    interpretParams();

    // calculate new image size
    int nW = originalSize.width +frameWidthSumE+frameWidthSumW+shadowWidthX+shadowExcessX;
    int nH = originalSize.height+frameWidthSumN+frameWidthSumS+shadowWidthY+shadowExcessY;

    Dimension dim = new Dimension(nW, nH);

    return dim;
  }

  // Implements JavaBean setter
  public void setBoWidth (String  s) { borderWidths=s;  invBoCache = true; };
  public void setBoWidthN(String  s) { borderWidthsN=s; invBoCache = true; };
  public void setBoWidthE(String  s) { borderWidthsE=s; invBoCache = true; };
  public void setBoWidthS(String  s) { borderWidthsS=s; invBoCache = true; };
  public void setBoWidthW(String  s) { borderWidthsW=s; invBoCache = true; };
  public void setBoTrans (String  s) { borderTranss=s;  invBoCache = true; };
  public void setBoCol   (String  s) { borderColors=s;  invBoCache = true; };
  public void setBoBlend (boolean b) { borderBlend=b;   invBoCache = true; };
  public void setBoMargin(int i)     { borderMargin=i;  invBoCache = true; };
  public void setBoClip  (int i)     { borderClip=i;    invBoCache = true; };

  public void setFrWidth (String  s) { frameWidths=s;   invFrCache = true; };
  public void setFrWidthN(String  s) { frameWidthsN=s;  invFrCache = true; };
  public void setFrWidthE(String  s) { frameWidthsE=s;  invFrCache = true; };
  public void setFrWidthS(String  s) { frameWidthsS=s;  invFrCache = true; };
  public void setFrWidthW(String  s) { frameWidthsW=s;  invFrCache = true; };
  public void setFrTrans (String  s) { frameTranss=s;   invFrCache = true; };
  public void setFrCol   (String  s) { frameColors=s;   invFrCache = true; };
  public void setFrBlend (boolean b) { frameBlend=b;    invFrCache = true; };

  public void setBrThick (int i)     { bracketThick=i;  invFrCache = true; };
  public void setBrX     (int i)     { bracketX=i;      invFrCache = true; };
  public void setBrY     (int i)     { bracketY=i;      invFrCache = true; };
  public void setBrCol   (String s)
  {
    bracketColor=Colors.getHTMLColor(s);
    invFrCache = true;
  };
  public void setBrStyle (String s)
  {
    bracketStyle=BRACKET_NONE;
    if (s.equalsIgnoreCase("NORMAL"))     { bracketStyle=BRACKET_NORM; };
    if (s.equalsIgnoreCase("HORIZONTAL")) { bracketStyle=BRACKET_HORZ; };
    if (s.equalsIgnoreCase("VERTICAL"))   { bracketStyle=BRACKET_VERT; };
    invFrCache = true;
  };

  public void setShWidth (int i)     { shadowWidth=i;   invShCache = true; };
  public void setShWidthX(int i)     { shadowWidthX_=i ;invShCache = true; };
  public void setShWidthY(int i)     { shadowWidthY_=i ;invShCache = true; };
  public void setShExX   (int i)     { shadowExcessX=i; invShCache = true; };
  public void setShExY   (int i)     { shadowExcessY=i; invShCache = true; };
  public void setShCol   (String s)
  {
    shadowColor=Colors.getHTMLColor(s);
    invShCache = true;
  };
  public void setShTrans (int i)     { shadowTrans=i;   invShCache = true; };
  public void setShDir   (String s)
  {
    shadowDir=SOUTH_EAST;
    if (s.equalsIgnoreCase("SOUTH_WEST")) { shadowDir=SOUTH_WEST; };
    if (s.equalsIgnoreCase("SOUTHWEST"))  { shadowDir=SOUTH_WEST; };
    if (s.equalsIgnoreCase("SW"))         { shadowDir=SOUTH_WEST; };
    if (s.equalsIgnoreCase("NORTH_WEST")) { shadowDir=NORTH_WEST; };
    if (s.equalsIgnoreCase("NORTHWEST"))  { shadowDir=NORTH_WEST; };
    if (s.equalsIgnoreCase("NW"))         { shadowDir=NORTH_WEST; };
    if (s.equalsIgnoreCase("NORTH_EAST")) { shadowDir=NORTH_EAST; };
    if (s.equalsIgnoreCase("NORTHEAST"))  { shadowDir=NORTH_EAST; };
    if (s.equalsIgnoreCase("NE"))         { shadowDir=NORTH_EAST; };
    invShCache = true;
  };
  public void setShDrop  (String s)
  {
    shadowDrop=DROP_SOFT;
    if (s.equalsIgnoreCase("SOFT")) { shadowDrop=DROP_SOFT; };
    if (s.equalsIgnoreCase("NORM")) { shadowDrop=DROP_NORM; };
    if (s.equalsIgnoreCase("HARD")) { shadowDrop=DROP_HARD; };
    invShCache = true;
  };

  public void setBgFile  (String s)  { bgFile=s;        invBgCache = true; };
  public void setBgOffX  (int x)     { bgOffsetX = x;   invBgCache = true; };
  public void setBgOffY  (int y)     { bgOffsetY = y;   invBgCache = true; };
  public void setBgCol   (String s)
  {
    bgColor=Colors.getHTMLColor(s);
    invBgCache = true;
  };
  public void setClip    (int i)     { clip=i;          invBgCache = true; };

  public void setDebug   (boolean d) { debug=d; };

  // Implements JavaBean getter
  public String  getBoWidth () { return borderWidths;   };
  public String  getBoWidthN() { return borderWidthsN;  };
  public String  getBoWidthE() { return borderWidthsE;  };
  public String  getBoWidthS() { return borderWidthsS;  };
  public String  getBoWidthW() { return borderWidthsW;  };
  public String  getBoTrans () { return borderTranss;   };
  public String  getBoCol   () { return borderColors;   };
  public boolean getBoBlend () { return borderBlend;    };
  public int     getBoMargin() { return borderMargin;   };
  public int     getBoClip  () { return borderClip;     };

  public String  getFrWidth () { return frameWidths;    };
  public String  getFrWidthN() { return frameWidthsN;   };
  public String  getFrWidthE() { return frameWidthsE;   };
  public String  getFrWidthS() { return frameWidthsS;   };
  public String  getFrWidthW() { return frameWidthsW;   };
  public String  getFrTrans () { return frameTranss;    };
  public String  getFrCol   () { return frameColors;    };
  public boolean getFrBlend () { return frameBlend;     };

  public String  getBrCol   () { return bracketColor.toString(); };
  public int     getBrThick () { return bracketThick;   };
  public int     getBrX     () { return bracketX;       };
  public int     getBrY     () { return bracketY;       };
  public String  getBrStyle ()
  {
    if (bracketStyle==BRACKET_NORM) { return "NORMAL";     };
    if (bracketStyle==BRACKET_HORZ) { return "HORIZONTAL"; };
    if (bracketStyle==BRACKET_VERT) { return "VERTICAL";   };
    return "NO_BRACKET";
  };

  public int     getShWidth () { return shadowWidth;    };
  public int     getShWidthX() { return shadowWidthX;   };
  public int     getShWidthY() { return shadowWidthY;   };
  public int     getShExX   () { return shadowExcessX;  };
  public int     getShExY   () { return shadowExcessY;  };
  public String  getShCol   () { return shadowColor.toString(); };
  public int     getShTrans () { return shadowTrans;    };
  public String  getShDir   ()
  {
    if (shadowDir==SOUTH_WEST) { return "SOUTH_WEST"; };
    if (shadowDir==NORTH_WEST) { return "NORTH_WEST"; };
    if (shadowDir==NORTH_EAST) { return "NORTH_EAST"; };
    return "SOUTH_EAST";
  };
  public String  getShDrop  ()
  {
    if (shadowDrop==DROP_NORM) { return "NORM"; };
    if (shadowDrop==DROP_HARD) { return "HARD"; };
    return "SOFT";
  };
  public String  getBgFile  () { return bgFile;             };
  public int     getBgOffX  () { return bgOffsetX;          };
  public int     getBgOffY  () { return bgOffsetY;          };
  public String  getBgCol   () { return bgColor.toString(); };
  public int     getClip    () { return clip;               };

  public boolean getDebug   () { return debug; };

  // implements JAFilter
  // this is the worker method
  public BufferedImage filter(BufferedImage bi, java.util.Map vars)
  {
    // initialization
    interpretParams();

    // all time cache update
    refreshCache(bi.getWidth(),bi.getHeight());

    // put all together
    // calculate new image size
    int nW = bi.getWidth() +frameWidthSumE+frameWidthSumW+shadowWidthX_W+shadowExcessX_W;
    int nH = bi.getHeight()+frameWidthSumN+frameWidthSumS+shadowWidthY_W+shadowExcessY_W;

    int dW = frameWidthSumW+shadowExcessX_W;
    int dH = frameWidthSumN+shadowExcessY_W;

    // create new image
    if (newImage != null) newImage.flush();
    newImage = new BufferedImage(nW,nH,BufferedImage.TYPE_INT_RGB);
    Graphics2D         g2d = newImage.createGraphics();
    g2d.setComposite(AlphaComposite.SrcOver);

    // background
    g2d.drawRenderedImage(backgroundCache,AffineTransform.getTranslateInstance(0,0));

    // shadow
    if (shadowParts>0)
    {
      g2d.drawRenderedImage(shadowCache,AffineTransform.getTranslateInstance(0,0));
    };

    // frame cache
    if (frameParts>0)
    {
      g2d.drawRenderedImage(frameCache,AffineTransform.getTranslateInstance(shadowExcessX_W,shadowExcessY_W));
    };

    // original image
    g2d.setClip(new RoundRectangle2D.Double(dW,dH,bi.getWidth(),bi.getHeight(),clip,clip));
    g2d.drawRenderedImage(bi,AffineTransform.getTranslateInstance(dW,dH));
    g2d.setClip(new Rectangle(0,0,nW,nH));

    // border cache
    if (borderParts>0)
    {
      g2d.drawRenderedImage(borderCache,AffineTransform.getTranslateInstance(dW,dH));
    };

    if (debug)
    {
      System.err.print("Freemem: "); System.err.print(Runtime.getRuntime().freeMemory());
      System.err.print("  tot.mem: "); System.err.println(Runtime.getRuntime().totalMemory());
    };

    g2d.dispose();
    bi.flush();
    newImage.flush();

    return newImage;
  }

  private void invalidateCache()
  {
    invBgCache = true;
    invBoCache = true;
    invFrCache = true;
    invShCache = true;
  };

  // cacheing logic
  // puts the necessary cached building blocks to the caches
  // and builds new blocks if necessary
  private void refreshCache(int w, int h)
  {
    // size changed
    if (lastCacheW != w)
    {
      lastCacheW = w;
      invBoCache = true;
      invBgCache = true;
      invShCache = true;
      invFrCache = true;
    };

    // size changed
    if (lastCacheH != h)
    {
      lastCacheH = h;
      invBoCache = true;
      invBgCache = true;
      invShCache = true;
      invFrCache = true;
    };

    // size or border params changed
    if (invBoCache)
    {
      createBorderCache(w,h);
      invBoCache = false;
    };

    // bg params changed
    if (invBgCache)
    {
      createBGCache(w,h);
      invBgCache = false;
      invShCache = true;
      invFrCache = true;
    };

    // fr params changed
    if (invFrCache)
    {
      createFrameCache(w,h);
      invFrCache = false;
      invShCache = true;
    };

    // sh params changed
    if (invShCache)
    {
      createShadowCache(w,h);
      invShCache = false;
    };

  };

  // create a new cached background
  private void createBGCache(int w, int h)
  {
    int nW = w+frameWidthSumE+frameWidthSumW+shadowWidthX_W+shadowExcessX_W;
    int nH = h+frameWidthSumN+frameWidthSumS+shadowWidthY_W+shadowExcessY_W;

    if (backgroundCache != null) backgroundCache.flush();
    backgroundCache = new BufferedImage(nW, nH, BufferedImage.TYPE_INT_RGB);
    Graphics2D  g2d = backgroundCache.createGraphics();

    g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION,RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
    g2d.setRenderingHint(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_QUALITY);
    g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING,RenderingHints.VALUE_COLOR_RENDER_QUALITY);

    // set Composite
    g2d.setComposite(AlphaComposite.Src);

    int wF=backgroundFragment.getWidth();
    int hF=backgroundFragment.getHeight();
    for (int i=0; i<=(backgroundCache.getWidth()+bgOffsetX)/wF; i++)
    {
      for (int j=0; j<=(backgroundCache.getHeight()+bgOffsetY)/hF; j++)
      {
        g2d.drawRenderedImage(backgroundFragment,AffineTransform.getTranslateInstance(i*wF-bgOffsetX,j*hF-bgOffsetY));
      };
    };
    g2d.dispose();
  };

  // create a new cached shadow
  private void createShadowCache(int w, int h)
  {
    int nW = w+frameWidthSumE+frameWidthSumW+shadowWidthX_W+shadowExcessX_W;
    int nH = h+frameWidthSumN+frameWidthSumS+shadowWidthY_W+shadowExcessY_W;

    int shWmax = max(shadowWidthX_W,  shadowWidthY_W);
        shWmax = max(shadowExcessX_W, shWmax);
        shWmax = max(shadowExcessY_W, shWmax);

    int r  = clip+shWmax;
    if (clip>0)  { r+=frameWidthSum; };

    if (shadowCache != null) shadowCache.flush();
    shadowCache = new BufferedImage(nW, nH, BufferedImage.TYPE_INT_ARGB);
    Graphics2D g2d = shadowCache.createGraphics();

    // set Composite
    g2d.setStroke(new BasicStroke(2.0f));
    g2d.setComposite(AlphaComposite.Src);

    g2d.setColor(shadowColor);

    float    fac = 0;

    switch (shadowDrop)
    {
      case DROP_SOFT: fac=3.0f/2; break;
      case DROP_NORM: fac=2.0f/2; break;
      case DROP_HARD: fac=4.0f/6; break;
    };

    int trans   = 0;
    int transMax=check(255-255*shadowTrans/100);
    int i;
    for (i=0; i<shWmax; i++)
    {
      Color c= new Color(shadowColor.getRed(), shadowColor.getGreen(), shadowColor.getBlue(), trans);
      g2d.setColor(c);
      g2d.drawRoundRect(i,i,nW-2*i,nH-2*i,r,r);
      trans=java.lang.Math.round((float)(transMax*(java.lang.Math.pow(1.0f*i/shWmax,fac))));
      if (r>0) { r--; };
    };
    g2d.drawRoundRect(i,i,nW-2*i,nH-2*i,r,r);
    Color c= new Color(shadowColor.getRed(), shadowColor.getGreen(), shadowColor.getBlue(), trans);
    g2d.setColor(c);
    g2d.fillRoundRect(i,i,nW-2*i,nH-2*i,r,r);
    g2d.dispose();
  };

  // create a new cached frame
  private void createFrameCache(int w, int h)
  {
    int   nW  = w+frameWidthSumW+frameWidthSumE;
    int   nH  = h+frameWidthSumN+frameWidthSumS;

    int   r   = clip;
    float dr  = 0;
    float drS = 0;

    BufferedImage blurred = new BufferedImage(nW, nH, BufferedImage.TYPE_INT_ARGB);
    BufferedImage canvas  = new BufferedImage(nW, nH, BufferedImage.TYPE_INT_ARGB);
    Graphics2D g2d        = canvas.createGraphics();

    g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION,RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
    g2d.setRenderingHint(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_QUALITY);
    g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING,RenderingHints.VALUE_COLOR_RENDER_QUALITY);
    g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,RenderingHints.VALUE_STROKE_NORMALIZE);

    // clear frame interior
    g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR,0.0f));
    g2d.fillRect(0,0,nW,nH);

    // set Composite
    g2d.setStroke(new BasicStroke(2.0f));
    g2d.setComposite(AlphaComposite.Src);

    if (r>0)
    {
      r += min(frameWidthSumN,min(frameWidthSumE,min(frameWidthSumS,frameWidthSumW)));
      dr = 1.0f*min(frameWidthSumN,min(frameWidthSumE,min(frameWidthSumS,frameWidthSumW)))/
           max(frameWidthSumN,max(frameWidthSumE,max(frameWidthSumS,frameWidthSumW)));
      drS=r;
    };

    int n0=0,e0=0,s0=0,w0=0;
    int tN=0,tE=0,tS=0,tW=0;
    shadowClip=0;

    for (int i=0; i<frameParts; i++)
    {
      tN = ((Integer)frameWidthN.get(i)).intValue();
      tE = ((Integer)frameWidthE.get(i)).intValue();
      tS = ((Integer)frameWidthS.get(i)).intValue();
      tW = ((Integer)frameWidthW.get(i)).intValue();
      int tMax=max(tN,max(tE,max(tS,tW)));
      Color c1=(Color)frameColor.get(i);
      Color c2=c1;
      int   tr1=check(255-255*((Integer)frameTrans.get(i)).intValue()/100);
      int   tr2=tr1;
      if (frameBlend)
      {
        c2=(Color)frameColor.get(i+1);
        tr2 = check(255-255*((Integer)frameTrans.get(i+1)).intValue()/100);
      }

      if (i == 0)
      {
        g2d.setColor(new Color(c1.getRed(),c1.getGreen(),c1.getBlue(),tr1));
        g2d.drawRoundRect(frameWidthSumW,frameWidthSumN,w-1,h-1,clip,clip);
      };

      for (int j=0; j<tMax; j++)
      {
        int red  =(tMax-j)*c1.getRed()/tMax  +j*c2.getRed()/tMax;
        int green=(tMax-j)*c1.getGreen()/tMax+j*c2.getGreen()/tMax;
        int blue =(tMax-j)*c1.getBlue()/tMax +j*c2.getBlue()/tMax;
        int trans=check((tMax-j)*tr1/tMax+j*tr2/tMax);
        g2d.setColor(new Color(red,green,blue,trans));

        g2d.drawRoundRect(w0+j*tW/tMax,n0+j*tN/tMax,nW-w0-e0-j*(tW+tE)/tMax-1,nH-n0-s0-j*(tN+tS)/tMax-1,r,r);

        if (r>0) { drS-=dr; shadowClip=r; r=java.lang.Math.round(drS); };
      };
      n0+=tN; e0+=tE; s0+=tS; w0+=tW;
    };

    // draw brackets
    if (bracketStyle != BRACKET_NONE)
    {
      g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION,RenderingHints.VALUE_ALPHA_INTERPOLATION_DEFAULT);
      g2d.setRenderingHint(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_DEFAULT);
      g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING,RenderingHints.VALUE_COLOR_RENDER_DEFAULT);

      g2d.setStroke(new BasicStroke(1.0f*bracketThick,BasicStroke.CAP_ROUND,BasicStroke.JOIN_ROUND));
      g2d.setColor(bracketColor);
      g2d.setComposite(AlphaComposite.Src);

      double gold=0.309;
      int    brX =(int)(gold*w);
      int    brY =(int)(gold*h);

      // set bracket dims
      if (bracketX >= 0) brX=min(w,bracketX);
      if (bracketX >= 0) brY=min(h,bracketY);

      // draw corners: NE,NW,SW,SE
      g2d.drawArc(nW-3*frameWidthSumE/2,frameWidthSumN/2,frameWidthSumE,frameWidthSumN-1,0,90);
      g2d.drawArc(frameWidthSumW/2,frameWidthSumN/2,frameWidthSumW-1,frameWidthSumN-1,90,90);
      g2d.drawArc(frameWidthSumW/2,nH-3*frameWidthSumS/2,frameWidthSumW-1,frameWidthSumS,180,90);
      g2d.drawArc(nW-3*frameWidthSumE/2,nH-3*frameWidthSumS/2,frameWidthSumE,frameWidthSumS,270,90);

      // draw lines
      if (bracketStyle == BRACKET_HORZ)
      {
        // draw horizontal lines: upper, lower
        g2d.drawLine(frameWidthSumW,frameWidthSumN/2,nW-frameWidthSumE,frameWidthSumN/2);
        g2d.drawLine(frameWidthSumW,nH-frameWidthSumS/2,nW-frameWidthSumE,nH-frameWidthSumS/2);
      } else
      {
        // draw horizontal lines: upper left, upper right
        g2d.drawLine(frameWidthSumW,frameWidthSumN/2,frameWidthSumW+brX,frameWidthSumN/2);
        g2d.drawLine(nW-frameWidthSumE,frameWidthSumN/2,nW-frameWidthSumE-brX,frameWidthSumN/2);
        // draw horizontal lines: lower left, lower right
        g2d.drawLine(frameWidthSumW,nH-frameWidthSumS/2,frameWidthSumW+brX,nH-frameWidthSumS/2);
        g2d.drawLine(nW-frameWidthSumE,nH-frameWidthSumS/2,nW-frameWidthSumE-brX,nH-frameWidthSumS/2);
      };
      if (bracketStyle == BRACKET_VERT)
      {
        // draw vertical lines: left, right
        g2d.drawLine(frameWidthSumW/2,frameWidthSumN,frameWidthSumW/2,nH-frameWidthSumS);
        g2d.drawLine(nW-frameWidthSumE/2,frameWidthSumN,nW-frameWidthSumE/2,nH-frameWidthSumS);
      } else
      {
        // draw vertical lines: upper left, upper right
        g2d.drawLine(frameWidthSumW/2,frameWidthSumN,frameWidthSumW/2,frameWidthSumN+brY);
        g2d.drawLine(nW-frameWidthSumE/2,frameWidthSumN,nW-frameWidthSumE/2,frameWidthSumN+brY);
        // draw vertical lines: lower left, lower right
        g2d.drawLine(frameWidthSumW/2,nH-frameWidthSumS,frameWidthSumW/2,nH-frameWidthSumS-brY);
        g2d.drawLine(nW-frameWidthSumE/2,nH-frameWidthSumS,nW-frameWidthSumE/2,nH-frameWidthSumS-brY);
      };

      g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION,RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
      g2d.setRenderingHint(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_QUALITY);
      g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING,RenderingHints.VALUE_COLOR_RENDER_QUALITY);
    };

    // clear frame interior
    g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR,0.0f));
    g2d.fillRoundRect(frameWidthSumW+1,frameWidthSumN+1,w-2,h-2,clip,clip);

    // put all together
    if (clip>0)
    {
      // smooth frame
      float blur[] = {0.05f,0.05f,0.05f,0.05f,0.6f,0.05f,0.05f,0.05f,0.05f};
      Kernel kernel=new Kernel(3,3,blur);
      ConvolveOp cop=new ConvolveOp(kernel,ConvolveOp.EDGE_NO_OP,null);
      cop.filter(canvas,blurred);
      canvas = blurred;
    }

    frameCache = canvas;
    blurred.flush();
    canvas.flush();
    g2d.dispose();
  };

  // create a new cached border
  private void createBorderCache(int w, int h)
  {

    if (borderCache != null) borderCache.flush();
    borderCache     = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
    Graphics2D  g2d = borderCache.createGraphics();

    int   r   = borderClip;
    float dr  = 0;
    float drS = 0;

    g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION,RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
    g2d.setRenderingHint(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_QUALITY);
    g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING,RenderingHints.VALUE_COLOR_RENDER_QUALITY);

    // clear border image
    g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR,0.0f));
    g2d.fillRect(0,0,w,h);

    // set Composite
    g2d.setStroke(new BasicStroke(2f));
    g2d.setComposite(AlphaComposite.Src);

    if (r>0)
    {
      r += min(borderWidthSumN,min(borderWidthSumE,min(borderWidthSumS,borderWidthSumW)));
      dr = 1.0f*min(borderWidthSumN,min(borderWidthSumE,min(borderWidthSumS,borderWidthSumW)))/
           max(borderWidthSumN,max(borderWidthSumE,max(borderWidthSumS,borderWidthSumW)));
      drS=r;
    };

    int n0=borderMargin,e0=borderMargin,s0=borderMargin,w0=borderMargin;

    for (int i=0; i<borderParts; i++)
    {
      int   tN = ((Integer)borderWidthN.get(i)).intValue();
      int   tE = ((Integer)borderWidthE.get(i)).intValue();
      int   tS = ((Integer)borderWidthS.get(i)).intValue();
      int   tW = ((Integer)borderWidthW.get(i)).intValue();
      int   tMax=max(tN,max(tE,max(tS,tW)));
      Color c1=(Color)borderColor.get(i);
      Color c2=c1;
      int   tr1=check(255-255*((Integer)borderTrans.get(i)).intValue()/100);
      int   tr2=tr1;
      if (borderBlend)
      {
        c2  = (Color)borderColor.get(i+1);
        tr2 = check(255-255*((Integer)borderTrans.get(i+1)).intValue()/100);
      }
      for (int j=0; j<tMax; j++)
      {
        int red  =check((tMax-j)*c1.getRed()/tMax  +j*c2.getRed()/tMax);
        int green=check((tMax-j)*c1.getGreen()/tMax+j*c2.getGreen()/tMax);
        int blue =check((tMax-j)*c1.getBlue()/tMax +j*c2.getBlue()/tMax);
        int trans=check((tMax-j)*tr1/tMax+j*tr2/tMax);
        if (borderClip>0) trans=check(((tMax-j)*tr1/tMax+j*tr2/tMax)/2f);
        g2d.setColor(new Color(red,green,blue,trans));
        g2d.drawRoundRect(w0+j*tW/tMax,n0+j*tN/tMax,w-w0-e0-j*(tW+tE)/tMax-1,h-n0-s0-j*(tN+tS)/tMax-1,r,r);
        if (r>borderClip) { drS-=dr; r=java.lang.Math.round(drS); };
      };
      n0+=tN; e0+=tE; s0+=tS; w0+=tW;
    };
    g2d.dispose();
  };


  // parameter interpretion, checking and adjusting
  private void interpretParams()
  {
    int             i;
    Integer         trans;
    Color           c;
    StringTokenizer tok;

    // border params
    if (invBoCache)
    {
      // boWidth
      borderWidth  = new Vector();
      borderWidthN = new Vector();
      borderWidthE = new Vector();
      borderWidthS = new Vector();
      borderWidthW = new Vector();

      borderWidthSum  = 0;
      borderWidthSumN = 0;
      borderWidthSumE = 0;
      borderWidthSumS = 0;
      borderWidthSumW = 0;

      // translate uniform border widths
      tok = new StringTokenizer(borderWidths, ",");
      while (tok.hasMoreTokens())
      {
        Integer width=Integer.valueOf(tok.nextToken());
        borderWidth.addElement(width);
        borderWidthN.addElement(width);
        borderWidthE.addElement(width);
        borderWidthS.addElement(width);
        borderWidthW.addElement(width);
        borderWidthSum+=width.intValue();
        borderWidthSumN+=width.intValue();
        borderWidthSumE+=width.intValue();
        borderWidthSumS+=width.intValue();
        borderWidthSumW+=width.intValue();
      }

      // boWidthN: replace values with N border widths
      i=0;
      tok = new StringTokenizer(borderWidthsN, ",");
      while (tok.hasMoreTokens())
      {
        Integer width=Integer.valueOf(tok.nextToken());
        if (borderWidthN.size() <= i) borderWidthN.addElement(width);
        borderWidthSumN-=(((Integer)borderWidthN.set(i,width)).intValue()-width.intValue());
        i++;
      }

      // boWidthE: replace values with E border widths
      i=0;
      tok = new StringTokenizer(borderWidthsE, ",");
      while (tok.hasMoreTokens())
      {
        Integer width=Integer.valueOf(tok.nextToken());
        if (borderWidthE.size() <= i) borderWidthE.addElement(width);
        borderWidthSumE-=(((Integer)borderWidthE.set(i,width)).intValue()-width.intValue());
        i++;
      }

      // boWidthS: replace values with S border widths
      i=0;
      tok = new StringTokenizer(borderWidthsS, ",");
      while (tok.hasMoreTokens())
      {
        Integer width=Integer.valueOf(tok.nextToken());
        if (borderWidthS.size() <= i) borderWidthS.addElement(width);
        borderWidthSumS-=(((Integer)borderWidthS.set(i,width)).intValue()-width.intValue());
        i++;
      }

      // boWidthW: replace values with W border widths
      i=0;
      tok = new StringTokenizer(borderWidthsW, ",");
      while (tok.hasMoreTokens())
      {
        Integer width=Integer.valueOf(tok.nextToken());
        if (borderWidthW.size() <= i) borderWidthW.addElement(width);
        borderWidthSumW-=(((Integer)borderWidthW.set(i,width)).intValue()-width.intValue());
        i++;
      }

      // min of all borderParts is effective borderParts
      borderParts=borderWidth.size();
      borderParts=min(borderParts,borderWidthN.size());
      borderParts=min(borderParts,borderWidthE.size());
      borderParts=min(borderParts,borderWidthS.size());
      borderParts=min(borderParts,borderWidthW.size());

      // boCol
      borderColor = new Vector();
      c = Color.white;

      // translate border colors
      tok = new StringTokenizer(borderColors, ",");
      while (tok.hasMoreTokens())
      {
        c = Colors.getHTMLColor(tok.nextToken());
        borderColor.addElement(c);
      }

      // set missing colors
      for (i=borderColor.size(); i<borderParts+1; i++)
      {
        borderColor.addElement(c);
      };

      // boTrans
      borderTrans = new Vector();

      // translate border transparencies
      tok  =new StringTokenizer(borderTranss, ",");
      trans=new Integer(0);
      while (tok.hasMoreTokens())
      {
        trans=Integer.valueOf(tok.nextToken());
        borderTrans.addElement(trans);
      }

      // set missing transparencies
      for (i=borderTrans.size(); i<borderParts+1; i++)
      {
        borderTrans.addElement(trans);
      };
    };

    // frame params
    if (invFrCache)
    {
      // frWidth
      frameWidth    = new Vector();
      frameWidthN   = new Vector();
      frameWidthE   = new Vector();
      frameWidthS   = new Vector();
      frameWidthW   = new Vector();

      frameWidthSum  = 0;
      frameWidthSumN = 0;
      frameWidthSumE = 0;
      frameWidthSumS= 0;
      frameWidthSumW = 0;

      // translate unique frame widths
      tok = new StringTokenizer(frameWidths, ",");
      while (tok.hasMoreTokens())
      {
        Integer width=Integer.valueOf(tok.nextToken());
        frameWidth.addElement(width);
        frameWidthN.addElement(width);
        frameWidthE.addElement(width);
        frameWidthS.addElement(width);
        frameWidthW.addElement(width);
        frameWidthSum+=width.intValue();
        frameWidthSumN+=width.intValue();
        frameWidthSumE+=width.intValue();
        frameWidthSumS+=width.intValue();
        frameWidthSumW+=width.intValue();
      }

      // frWidthN: replace values with N frame widths
      i=0;
      tok = new StringTokenizer(frameWidthsN, ",");
      while (tok.hasMoreTokens())
      {
        Integer width=Integer.valueOf(tok.nextToken());
        if (frameWidthN.size() <= i) frameWidthN.addElement(width);
        frameWidthSumN-=(((Integer)frameWidthN.set(i,width)).intValue()-width.intValue());
        i++;
      }

      // frWidthE: replace values with E frame widths
      i=0;
      tok = new StringTokenizer(frameWidthsE, ",");
      while (tok.hasMoreTokens())
      {
        Integer width=Integer.valueOf(tok.nextToken());
        if (frameWidthE.size() <= i) frameWidthE.addElement(width);
        frameWidthSumE-=(((Integer)frameWidthE.set(i,width)).intValue()-width.intValue());
        i++;
      }

      // frWidthE: replace values with S frame widths
      i=0;
      tok = new StringTokenizer(frameWidthsS, ",");
      while (tok.hasMoreTokens())
      {
        Integer width=Integer.valueOf(tok.nextToken());
        if (frameWidthS.size() <= i) frameWidthS.addElement(width);
        frameWidthSumS-=(((Integer)frameWidthS.set(i,width)).intValue()-width.intValue());
        i++;
      }

      // frWidthW: replace values with W frame widths
      i=0;
      tok = new StringTokenizer(frameWidthsW, ",");
      while (tok.hasMoreTokens())
      {
        Integer width=Integer.valueOf(tok.nextToken());
        if (frameWidthW.size() <= i) frameWidthW.addElement(width);
        frameWidthSumW-=(((Integer)frameWidthW.set(i,width)).intValue()-width.intValue());
        i++;
      }

      // min of all frameParts is effective frameParts
      frameParts=frameWidth.size();
      frameParts=min(frameParts,frameWidthN.size());
      frameParts=min(frameParts,frameWidthE.size());
      frameParts=min(frameParts,frameWidthS.size());
      frameParts=min(frameParts,frameWidthW.size());

      // frCol
      frameColor = new Vector();
      c = Color.white;

      // translate frame colors
      tok = new StringTokenizer(frameColors, ",");
      while (tok.hasMoreTokens())
      {
        c = Colors.getHTMLColor(tok.nextToken());
        frameColor.addElement(c);
      }

      // set missing colors
      for (i=frameColor.size(); i<frameParts+1; i++)
      {
        frameColor.addElement(c);
      };

      // frTrans
      frameTrans = new Vector();

      // translate frame transparencies
      tok  =new StringTokenizer(frameTranss, ",");
      trans=new Integer(0);
      while (tok.hasMoreTokens())
      {
        trans=Integer.valueOf(tok.nextToken());
        frameTrans.addElement(trans);
      }

      // set missing transparencies
      for (i=frameTrans.size(); i<frameParts+1; i++)
      {
        frameTrans.addElement(trans);
      };
    };

    // background params
    if (invBgCache)
    {
      // shBGFile & shBGCol
      // try to load image from path
      int           w,h;
      File          file  = null;
      URL           url   = null;
      BufferedImage image = null;

      if ((bgFile != null) && (bgFile != ""))
      {
        try
        {
          // Read from a file
          file = new File(bgFile);
          // if readable, convert to a url
          if (file.canRead())
          {
            url = file.toURL();
          }
          else
          // try it as a url
          {
            url  = new URL(bgFile);
          }
        }
        // url conversion failed
        catch (MalformedURLException mue)
        {
          if (debug)
          {
            System.err.print("XBorderFilter was unable to read bgFile: "); System.out.println(bgFile);
          };
        };

        // try as modern java
        try
        {
          image = ImageIO.read(url);
        }
        // url not found
        catch (IOException ioe)
        {
          if (debug)
          {
            System.err.print("XBorderFilter was unable to read url: "); System.out.println(url);
          };
        }
        // try a java 1.3 compatible solution
        catch (NoClassDefFoundError ncd)
        {
          if (debug)
          {
            System.err.println("XBorderFilter fall back to ImageIcon ");
          };

          ImageIcon ii = null;

          if (url != null) ii = new ImageIcon(url);

          if (ii != null)
          {
            image = new BufferedImage(ii.getIconWidth(),ii.getIconHeight(),BufferedImage.TYPE_INT_RGB);
            image.createGraphics().drawImage(ii.getImage(),0,0,null);
            ii.getImage().flush();
          }
          else
          {
            if (debug)
            {
              System.err.print("XBorderFilter was unable to construct ImageIcon from: "); System.out.println(url);
            };
          }
        }
        // anything else
        catch (Exception ioe)
        {
          if (debug)
          {
            System.err.print("XBorderFilter was unable to construct ImageIO from: "); System.out.println(url);
          };
        };
      };

      if (image == null)
      {
        image = new BufferedImage(100,100, BufferedImage.TYPE_INT_RGB);
        Graphics2D     g2d = image.createGraphics();
        g2d.setComposite(AlphaComposite.SrcOver);
        g2d.setColor(bgColor);
        g2d.fillRect(0,0,100,100);
      }
      // make background fragment
      w=image.getWidth(); h=image.getHeight();
      if (backgroundFragment != null) backgroundFragment.flush();
      backgroundFragment = new BufferedImage(w,h, BufferedImage.TYPE_INT_RGB);
      Graphics2D     g2d = backgroundFragment.createGraphics();
      g2d.setComposite(AlphaComposite.SrcOver);
      g2d.drawRenderedImage(image, AffineTransform.getTranslateInstance(0, 0));
      image.flush();
      g2d.dispose();
    };


    // shadow params
    if (invShCache)
    {
      // shWidth
      // translate shadow width(s)
      shadowWidthX=shadowWidth;
      shadowWidthY=shadowWidth;

      if (shadowWidthX_ != -1)
      {
        shadowWidthX =shadowWidthX_;
      }
      if (shadowWidthY_ != -1)
      {
        shadowWidthY =shadowWidthY_;
      }

      shadowWidthX_W=max(shadowWidthX,0);
      shadowWidthY_W=max(shadowWidthY,0);

      // shEx
      shadowExcessX_W=min(shadowWidthX_W,shadowExcessX);
      shadowExcessY_W=min(shadowWidthY_W,shadowExcessY);

      // swap shadow values according to direction
      if (shadowDir == SOUTH_WEST)
      {
        int x=shadowWidthX_W; shadowWidthX_W=shadowExcessX_W; shadowExcessX_W=x;
      };
      if (shadowDir == NORTH_WEST)
      {
        int x=shadowWidthX_W; shadowWidthX_W=shadowExcessX_W; shadowExcessX_W=x;
        int y=shadowWidthY_W; shadowWidthY_W=shadowExcessY_W; shadowExcessY_W=y;
      };
      if (shadowDir == NORTH_EAST)
      {
        int y=shadowWidthY_W; shadowWidthY_W=shadowExcessY_W; shadowExcessY_W=y;
      };

      if ((shadowWidthX_W+shadowWidthY_W+shadowExcessX_W+shadowExcessY_W)>0)
      {
        shadowParts=1;
      };

    };
  };

  // helpers
  int  max(int a, int b)  { return java.lang.Math.max(a,b); };
  int  min(int a, int b)  { return java.lang.Math.min(a,b); };
  void swap(int a, int b) { int x=a; a=b; b=x;              };
  int  check(float a)     { return min(max(java.lang.Math.round(a),0),255); };

  // to be up to date
  private final String  versionString      = "XBorderFilter V0.96 - 2005 J.Troeger";

  // constants
  private final int SOUTH_EAST      = 1;
  private final int SOUTH_WEST      = 2;
  private final int NORTH_WEST      = 3;
  private final int NORTH_EAST      = 4;

  private final int DROP_HARD       = 4;
  private final int DROP_NORM       = 3;
  private final int DROP_SOFT       = 2;

  private final int BRACKET_NONE    = 0;
  private final int BRACKET_NORM    = 1;
  private final int BRACKET_HORZ    = 2;
  private final int BRACKET_VERT    = 3;

  // internals
  private boolean   invBgCache      = true;
  private boolean   invBoCache      = true;
  private boolean   invFrCache      = true;
  private boolean   invShCache      = true;


  // actual caches
  private BufferedImage backgroundCache    = null;
  private BufferedImage borderCache        = null;
  private BufferedImage frameCache         = null;
  private BufferedImage shadowCache        = null;
  private BufferedImage newImage           = null;

  // cache adds
  private BufferedImage backgroundFragment = null;
  private int           lastCacheW  = 0;
  private int           lastCacheH  = 0;


  // variables with default values
  // for borders
  private Vector  borderWidth       = new Vector();
  private String  borderWidths      = "";
  private int     borderWidthSum    = 0;
  private Vector  borderWidthN      = new Vector();
  private String  borderWidthsN     = "";
  private int     borderWidthSumN   = 0;
  private Vector  borderWidthE      = new Vector();
  private String  borderWidthsE     = "";
  private int     borderWidthSumE   = 0;
  private Vector  borderWidthS      = new Vector();
  private String  borderWidthsS     = "";
  private int     borderWidthSumS   = 0;
  private Vector  borderWidthW      = new Vector();
  private String  borderWidthsW     = "";
  private int     borderWidthSumW   = 0;

  private Vector  borderColor       = new Vector();
  private String  borderColors      = "white";

  private Vector  borderTrans       = new Vector();
  private String  borderTranss      = "50";

  private int     borderMargin      = 0;
  private boolean borderBlend       = false;
  private int     borderParts       = 0;

  private int     borderClip        = 0;

  // for frames
  private Vector  frameWidth        = new Vector();
  private String  frameWidths       = "";
  private int     frameWidthSum     = 0;
  private Vector  frameWidthN       = new Vector();
  private String  frameWidthsN      = "";
  private int     frameWidthSumN    = 0;
  private Vector  frameWidthE       = new Vector();
  private String  frameWidthsE      = "";
  private int     frameWidthSumE    = 0;
  private Vector  frameWidthS       = new Vector();
  private String  frameWidthsS      = "";
  private int     frameWidthSumS    = 0;
  private Vector  frameWidthW       = new Vector();
  private String  frameWidthsW      = "";
  private int     frameWidthSumW    = 0;

  private Vector  frameColor        = new Vector();
  private String  frameColors       = "white";

  private Vector  frameTrans        = new Vector();
  private String  frameTranss       = "0";

  private int     frameParts        = 0;
  private boolean frameBlend        = false;

  private int     clip              = 0;

  // for brackets
  private int     bracketThick      = 1;
  private int     bracketX          = -1;
  private int     bracketY          = -1;
  private Color   bracketColor      = Color.black;
  private int     bracketStyle      = BRACKET_NONE;

  // for shadows
  private int     shadowWidth       = 0;
  private int     shadowWidthX      = 0;
  private int     shadowWidthY      = 0;
  private int     shadowWidthX_     = -1;
  private int     shadowWidthY_     = -1;
  private int     shadowWidthX_W    = 0;
  private int     shadowWidthY_W    = 0;
  private int     shadowExcessX     = 0;
  private int     shadowExcessY     = 0;
  private int     shadowExcessX_W   = 0;
  private int     shadowExcessY_W   = 0;

  private String  bgFile            = "";
  private int     bgOffsetX         = 0;
  private int     bgOffsetY         = 0;
  private Color   bgColor           = Color.white;
  private Color   shadowColor       = Color.gray;
  private int     shadowTrans       = 0;

  private int     shadowDir         = SOUTH_EAST;
  private int     shadowDrop        = DROP_NORM;
  private int     shadowParts       = 0;

  private int     shadowClip        = 0;

  private boolean debug             = false;

};

