[an error occurred while processing this directive] [an error occurred while processing this directive]

(6) 背景を書いたり、影をつけたり豪華にする

前回は新たに全部の写真が並んだ、一覧表示モードを追加しました。
前回 から変わったのは、以下の点です。

Env: 色々な定数を入れるクラス

初期化をIvylet の中で行うことにしたので、初期化が無くなりました。 あと、背景に画像を使うようになったので BACKGROUND_URL が増えました。
class Env {
    public static int    W, H, BTN_W;
    public static int    T_MAX;
    public static int    REFRESH;
    public static double WH_RATIO;
    public static String URL, BACKGROUND_URL;
}

Ivylet: メインのアプレットのクラス

色んなパラメータを、Java で埋め込むのではなく、htmlの <param> タグから読むようにしました。 これにより、出来たクラスファイル(あるいは jar ファイル) だけ持って行って、適当に設定を書いた html を作ると、 色んな写真集を作れるようになります。
パラメータの読み込みは簡単で、JApplet クラスについてるgetParameter()を使えばOK です。 ただ、そのままでは全部文字列なので、数値のものはなおしてやります。
public class Ivylet extends JApplet {
    public void init() {
        loadParameter();
        String[] imgPaths = extractPaths(Env.URL);
        IvyPanel tp = new IvyPanel(imgPaths);
        setContentPane(tp);
        new Timer(Env.REFRESH, tp).start();
    }

    /** 各種パラメータのデフォルト値を設定 */
    void loadParameter() {
        Env.W = getIntParameter("width", 800);
        Env.H = getIntParameter("height", 600);
        Env.BTN_W = getIntParameter("button_width", 30);
        Env.T_MAX = getIntParameter("t_max", 20);
        Env.WH_RATIO = getDoubleParameter("wh_ratio", 1.333333);
        Env.REFRESH = getIntParameter("refresh", 15);
        Env.URL = getParameter("url", "http://funini.com/kei/ivy/narita/");
        Env.BACKGROUND_URL = getParameter("background_url",
                "http://funini.com/kei/ivy/imgs/back.jpg");
    }

    /** param タグから String のパラメータを取得。失敗時はデフォルト値を返す */
    String getParameter(String key, String defval) {
        String s = getParameter(key);
        return (s == null) ? defval : s;
    }

    /** param タグから int のパラメータを取得。失敗時はデフォルト値を返す */
    int getIntParameter(String key, int defval) {
        try {
            String s = getParameter(key);
            return (s == null) ? defval : Integer.parseInt(s);
        } catch (Exception e){ return defval; }
    }

    /** param タグから、double のパラメータを取得。失敗時はデフォルト値を返す */
    double getDoubleParameter(String key, double defval) {
        try {
            String s = getParameter(key);
            return (s == null) ? defval : Double.parseDouble(s);
        } catch (Exception e) { return defval; }
    }

    /** 指定されたパスから、画像の URL 一覧を取得 */
    public String[] extractPaths(String urlStr) {
        // urlBase には、URLの / 以前の部分が入る ( http://funini.com/kei/ など)
        String urlBase = urlStr.substring(0, urlStr.lastIndexOf("/") + 1);
        ArrayList files = new ArrayList();
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(
                    new URL(urlStr).openConnection().getInputStream()));
            extractFiles(files, urlBase, br);
        } catch (IOException ie) {
            ie.printStackTrace();
        }
        return (String[]) files.toArray(new String[0]);
    }

    /** br から全体を読み込み、画像の URL 一覧を取得 */
    void extractFiles(ArrayList files, String urlBase, BufferedReader br) throws IOException{
        Pattern pattern = Pattern.compile("^(.+\\.(jpg|JPG))<.*");
        for(;;) {
            String line = br.readLine();
            if(line == null) break;
            String[] targets = line.split(">");
            for(int i = 0; i < targets.length; i++) {
                Matcher m = pattern.matcher(targets[i]);
                if(m.matches()) files.add(urlBase + m.group(1));
            }
        }
    }
}

IvyPanel: メインのパネル

画面遷移のときに画面全体に被せる影と、背景を追加しました。 詳しい説明は、それぞれのクラスの説明をどうぞ。
class IvyPanel extends JPanel implements ActionListener, MouseListener {
    Button      button;   // ボタンです。クリックすると表示モードが変わります
    Shadow      shadow;   // 全体に被せる影です。モードが変わるときに描画されます。
    Background  back;     // 背景です。
    IconList    iconList; // 各画像の重ね順です。
    boolean    listmode; // 表示モードです。「一覧表示(true)」と「重ね表示(false)」があります

    public IvyPanel(String[] urls) {
        /** コンストラクタ 引数は画像が入っているパス */
        setBackground(new Color(0x33, 0x33, 0x33)); // 背景色設定
        setPreferredSize(new Dimension(Env.W, Env.H)); // サイズ設定

        button = new Button(0, Env.H - Env.BTN_W, 210, Env.BTN_W); // ボタンを配置
        shadow = new Shadow(Env.W, Env.H); // 影作成
        back   = new Background(Env.BACKGROUND_URL, Env.W, Env.H);
        iconList = new IconList(urls);
        listmode = false;
        addMouseListener(this); // マウスのイベントを扱えるようにする
    }

    public synchronized void mousePressed(MouseEvent e) {
        /** マウスがクリックされたときのイベント */
        Point p = e.getPoint();
        if(button.hit(p)) {       // ボタンがクリックされた場合
            listmode = !listmode; // 一覧表示 と 重ね表示を切り替え
            initView();           // 初期表示
            return;
        }

        // ボタン以外がクリックされた時
        for(int i = iconList.size() - 1; i >= 0; i--) { // クリックは表示と逆順で調べる
            Icon icon = iconList.get(i); // i 番目のアイコンについて
            if(icon.hit(p)){ // クリックされている場合
                if(listmode) 
                    iconList.listTop(icon); // icon をトップに表示 (一覧表示)
                else 
                    iconList.piledTop(icon); // icon をトップに表示 (重ね表示)
                return;
            }
        }
        // ボタンもアイコンもクリックされていない場合、初期表示に戻る
        initView();
    }

    void initView() {
        /** 初期表示を行う */
        if(listmode) iconList.list(); // 一覧表示
        else         iconList.pile(); // 重ね表示
        shadow.animationStart(); // 影を表示
    }

    /** タイマイベントが発生したら、時間を進める */
    public void actionPerformed(ActionEvent ae) 
    {   addTime(); }

    /** 時間を進める (何度も呼ばれると困るから、synchronized にしました) */
    synchronized void addTime() {
        for(int i = 0; i < iconList.size(); i++) // 全てのアイコンについて
            iconList.get(i).addTime(); // 時間を進める
        shadow.addTime(); // 影も時間を進める
        repaint(); // 再描画
    }

    /** 描画を行う */
    public void paintComponent(Graphics g) {
        super.paintComponent(g); // ウィンドウを表示
        Graphics2D g2 = (Graphics2D) g; // アンチエイリアスの設定など
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
        back.draw(g2); // 背景を表示
        button.draw(g2); // ボタンを表示
        for(int i = 0; i < iconList.size(); i++)
            iconList.get(i).draw(g2); // 各アイコンを表示
        shadow.draw(g2); // 影を表示
    }

    /** MouseListener を実装するために必要なメソッド群 */
    public void mouseReleased(MouseEvent e) {}
    public void mouseEntered(MouseEvent e) {}
    public void mouseExited(MouseEvent e) {}
    public void mouseClicked(MouseEvent e) {}
}

IconList: アイコンのリスト、重ね順、位置リストなどを保持するクラス

変化無いので省略します。

IconPosition: アイコンの位置を持つクラス

変化無いので省略します。

PiledPosition: 重ね表示のアイコンの位置を持つクラス

変化無いので省略します。

ListPosition: 一覧表示のアイコン位置を持つクラス

変化ないので省略します。

Rect: パネルに置く色んな部品の基本クラス

変化無いので省略します。

Icon: アイコンクラス

変化ありません。
class Icon extends Rect {
    int[] dest, orig; // 移動先と移動元のx, y, w, hを持つ配列
    public Icon(String imgPath) {
        super(0, 0, 0, 0);
        loadImage(imgPath);
        col = new Color(0.85f, 0.85f, 0.75f, 0.3f);
        dest = new int[] { 0, 0, 0, 0 };
        orig = new int[] { 0, 0, 0, 0 };
    }

    /** アニメーションの目的地を設定 */
    public void setDestination(int[] A) {
        for(int i = 0; i < 4; i++) {
            orig[i] = P[i];
            dest[i] = A[i];
        }
        animationStart(); // 時間変数 t,state を初期化
    }

    /** 時間を進め、state に応じて、このオブジェクトを移動させる。 
     * 移動元は orig, 移動先は dest。*/
    public void addTime() {
        super.addTime();
        for(int i = 0; i < 4; i++)
            P[i] = (int) (orig[i] * (1 - state) + dest[i] * state);
    }

    /** アイコンを描画する */
    void draw(Graphics2D g) {
        // まずは、画像が半分以上画面外にはみ出ている場合は描画しない
        if(P[0] + P[2] / 2 < 0 || P[1] + P[3] / 2 < 0 
                || Env.W <= P[0] || Env.H <= P[1]) return;
        g.setColor(col);
        // アイコンの周りの、少し透明な枠を描画する
        g.fillRect(P[0], P[1], P[2], P[3]);
        // 少し余白をあけて、アイコンを描画する
        int marginX = P[2] / 30;
        int marginY = P[3] / 30;
        g.drawImage(img, P[0] + marginX, P[1] + marginY, P[0] + P[2] - marginX,
                P[1] + P[3] - marginY, 0, 0, imgW, imgH, null);
    }
}

Button: ボタンクラス

変化ないので省略します。

[New]Shadow: 画面全体を暗くする影

モード変更時や、重ね表示で初期表示に戻るときに、 ちょっと透明にして、濃い茶色の四角を上から描画することで、画面全体が遷移している感を出します。

class Shadow extends Rect {
    public Shadow(int w, int h) { super(0, 0, w, h); }

    public void draw(Graphics2D g) {
        /** 影を全体に描画する */
        if(t == Env.T_MAX) return; // 影が終了していれば return
        // 透明度を計算する
        float alpha = (float)(0.5 - 2 * Math.pow(state - 0.5, 2));
        g.setColor(new Color(0.6f, 0.4f, 0.2f, alpha));
        // 全体を塗りつぶす
        g.fillRect(0, 0, Env.W, Env.H);
    }
}

[New] Background: 背景 (指定された画像を並べて表示)

背景を作りました。四角いタイル上の背景(こんなの) を、Env.W と Env.H だけ並べて表示します。

class Background extends Rect {
    /** コンストラクタ。引数: 画像のURL(またはパス)、幅、高さ*/
    public Background(String url, int w, int h) {
        super(0, 0, w, h);
        loadImage(url);
    }

    /** img にロードされた画像を、指定された幅・高さを満たすよう敷き詰める */
    public void draw(Graphics2D g) {
        int n = (int) Math.ceil((double) P[2] / imgW);
        int m = (int) Math.ceil((double) P[3] / imgH);
        for(int i = 0; i < n; i++)
            for(int j = 0; j < m; j++)
                g.drawImage(img, i * imgW, j * imgH, null);
    }
}

ImageLoader: 画像を読み込むためのクラス

前回から変更無いので省略します。
[an error occurred while processing this directive]