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

/**
 * Title:        JAlbum filter to add a variety of borders to images.
 * Copyright:    Copyright (c) 2004-2005
 * @author       Jens Troeger. Slightly modified by David Ekholm/TomCee
 * @version      0.96
 */
public class XBorderFilter implements JAFilter, ModifiesSize {

    // Parameterless constructor
    public XBorderFilter() {
        System.out.println(versionString);  //--> sorry Jens, but gives to much output!
        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;
    }

    //to prevent console warnings
    private static String toHTMLColor(Color c) {
        return "#" + Integer.toHexString(c.getRGB()).substring(2);
    }

    // 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 toHTMLColor(bracketColor);
    }

    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 toHTMLColor(shadowColor);
    }

    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 toHTMLColor(bgColor);
    }

    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);
                /* ************* outcommented by @Tom to prevent the boclip to double the transparancy   ************************************ */

                /*if (borderClip > 0) {
                trans = check(((tMax - j) * tr1 / tMax + j * tr2 / tMax) / 2f);
                }*/

                /* ************* outcommented by @Tom to prevent the boclip to double the transparancy   ************************************ */
                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 != ""))
            if (!(bgFile.equals(null)) && !(bgFile.equals(""))) {
                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;
}

