2011年8月29日月曜日

SyntaxHighlighterでソースコードを綺麗に見せよう

どうも。
いち、プログラマのyu1rowです。
ソースコードを載せることもあるブログとしては、綺麗に見せたいですよね。
というわけで、ソースコードを綺麗に整形してくれる"SyntaxHighlighter"を導入することにしました。
実際にBloggerに導入したので、手順をメモします。

どうすりゃいいの?


基本的には3ステップです。
  1. SyntaxHighlighterのダウンロード
  2. ファイルをどこかにアップロード
  3. blog内容を編集

Step.1 ダウンロード


↓以下のサイトからダウンロードしました。
SyntaxHighlighter ダウンロード先(2011/08/28現在)
ちなみに、このときのバージョンは3.0.83。

ダウンロードしたファイルを展開すると、いくつかファイルとフォルダが現れます。
以下の2つのフォルダと、中のファイルを使います。
./scriptsjsファイルが収められています
./stylescssファイルが収められています

Step.2 アップロード


ここに一番時間がかかったように思います。
というのも、Bloggerにはファイルをアップロードすることができず、別のスペースを借りることにしたのですが、Bloggerと同じくgoogleが運営する「サイト」を使ってみたところ、分かりづらくて使いづらくて...
まぁ他に使い慣れているスペースがあるならば、そちらを検討するのもテです。

以下の3つのファイルだけで最低限のことはできますので、お好みのスペースにアップしてください。
./scripts/shCore.js 基本のスクリプトファイルです。
./scripts/shBrushJava.js Javaのソースを整形します。
C#のソースを整形したければ"shBrushCSharp.js"を使います。
他に使用できる言語はここを参照してください。
./styles/shCoreDefault.css 基本のスタイルです。
他のスタイルも色々あるみたいですが、未検証です。

Step.3 blogを編集


最後にblogを編集します。
上記の3つのファイルを<script>タグと<link>タグで<head>タグの中に埋め込みます。
その後に、<script>タグでSyntaxHighlighter.all()という関数を動かすようにします。これで準備完了。

あとは、装飾したいコードを<pre>タグで囲みます。
<pre>タグの"class"属性には"brush: XXXX;"と書きます。
例えばJavaのコードなら、"XXXX"の部分は"java"と書きます。

以下に記述する例を載せておきます。
<html>
<head>
<title>コード装飾の例</title> 
<script type="text/javascript" src="http://yoursite.com/scripts/shCore.js"></script>
<script type="text/javascript" src="http://yoursite.com/scripts/shBrushJava.js"></script>
<link type="text/css" rel="stylesheet" href="http://yoursite.com/styles/shCoreDefault.css"/>
<script type="text/javascript">SyntaxHighlighter.all();</script>
</head> 
 
<body>
<pre class="brush: java;">
public class Hoge {
/** デフォルトコンストラクタです。 */
public Hoge() { } 
}
</pre>
</body>
</html>
  • 4~7行目は順番が重要かもしれません
  • 他の言語を増やしたい場合、5行目の<script>タグを必要に応じて増やします。
    他に使用できる言語については、ここを参照してください。
  • Bloggerの場合は、「デザイン」⇒「HTML の編集」で<head>タグの中に埋め込むようにしました。
これでコードを美しく表示することができました。
他にもテーマだとか、コードを別ウインドウで表示する?機能だとか色々できるみたいです。
皆様それぞれに工夫してみてください。

2011年8月25日木曜日

文字列を中央に描画する

文字列を中央に描画...がうまくできなかった話


どうも。
いち、プログラマのyu1rowです。

例えば画像の中央に文字列を描画したい場合、java.awt.FontMetricsクラスを用いて、文字列が描画されたときの全体のサイズを取得して実現できます。
しかし、これで取得した領域で計算すると、文字がちょっと上にずれます。というか、ずれて見えます。
これは文字列の「ベースライン」を考慮していないからのようです。

とある四角形を、とある領域の真ん中に描きたい場合、描画開始位置の計算は、普通は以下のようになります。
  • x = ( 領域の幅   - 四角形の幅   ) / 2;
  • y = ( 領域の高さ - 四角形の高さ ) / 2;
しかし、java.awt.Graphics.drawString()は「ベースライン」を描画開始位置に合わせてしまいますので、y座標を補正する必要があります。
方法は、ベースラインから文字の上端までの距離をy座標に足すだけで、距離の取得はFontMetrics.getMaxAscent()を使用します。

補正後の描画開始位置の計算は、以下の通り。
  • x = ( 領域の幅   - 四角形の幅   ) / 2;
  • y = ( 領域の高さ - 四角形の高さ ) / 2 + FontMetrics.getMaxAscent();

御託は置いといて


はい、ソースどーん!
package com.yu1row.blog;

import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;

public class Practice01 {

    public static void main(String[] args) throws IOException {
        Rectangle size = new Rectangle(200, 30);
        BufferedImage image = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_RGB);
        drawStringCenter(image.getGraphics(), size, "よろしくお願いしまーす!");
        ImageIO.write(image, "PNG", new File("center.png"));
    }

    public static void drawStringCenter(Graphics g, Rectangle rect, String text) {
        // Calc draw position
        FontMetrics fm = g.getFontMetrics();
        Rectangle rectText = fm.getStringBounds(text, g).getBounds();
        int x = (rect.width - rectText.width) / 2;
        int y = (rect.height - rectText.height) / 2 + fm.getMaxAscent();
        // Draw text
        g.drawString(text, x, y);
    }
}

アラビア文字など、右から左に書かれる文字列がどうなるのかとか、面倒なんで未検証です。

2011年8月19日金曜日

画像を色抜き合成する

「画像を重ねてよ」って言われて、どうしようかって思ったんです


どうも。
いち、プログラマのyu1rowです。
2枚のBufferedImageがありまして、2枚目の画像の特定の色を透明にしてしまいたいという要件がありました。
なんせあんまりJavaの画像処理のコトしらないし、とりあえず以下のソースのように対処しました。
package com.yu1row.blog;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.awt.image.MemoryImageSource;
import java.awt.image.PixelGrabber;

public class Practice02 {
    /**
     * 画像同士を合成した {@link BufferedImage} を取得します。
     * <p>元画像に対し、重ね合わせ画像の特定の色を透明にし、元画像に重ね合わせて合成画像を得ます。</p>
     * @param src 元画像
     * @param lay 重ね合わせ画像
     * @param alphaColor 重ね合わせ画像の透明色
     * @return 合成画像
     */
    public static BufferedImage getOverlayedImage(BufferedImage src, BufferedImage lay, Color alphaColor) {
        if (src == null) { return null; }   // 元画像がnullの場合
        if (lay == null) { return src; } // 重ね合わせ画像がnullの場合

        // 重ね合わせ画像のサイズ取得
        int width = lay.getWidth();
        int height = lay.getHeight();
        // 重ね合わせ画像→int[]変換
        int[] pixel = new int[width*height];
        PixelGrabber pg = new PixelGrabber(lay,0,0,width,height,pixel,0,width);
        try { pg.grabPixels(); } catch (InterruptedException e) { return null; }
        // 重ね合わせ画像の色抜き
        int color = alphaColor.getRGB();
        for(int i = 0; i < width * height; ++i) {
            if (pixel[i] == color) {
                pixel[i] = pixel[i] & 0x00FFFFFF;
            }
        }

        // 色抜きされたイメージを作成
        Image alphaImage = Toolkit.getDefaultToolkit().createImage(new MemoryImageSource(width, height, pixel, 0, width));

        // 結果画像を作成
        // 元画像のクローンに、色抜きイメージを描画してオーバーレイを行う
        BufferedImage result = getBufferedImageClone(src);
        Graphics g = result.getGraphics();
        g.drawImage(alphaImage,0,0,null);
        g.dispose();

        return result;
    }

    /**
    * {@link BufferedImage} のクローンを取得します。
    * @param src 元の {@link BufferedImage} オブジェクト
    * @return {@link BufferedImage} のクローン
    */
    public static BufferedImage getBufferedImageClone(BufferedImage src) {
        if (src == null) { return null; }

        // 同サイズ、同タイプのBufferedImageを作成
        BufferedImage dst = new BufferedImage(src.getWidth(), src.getHeight(), src.getType());

        // クローンに描画
        Graphics g = dst.getGraphics();
        g.drawImage(src, 0, 0, src.getWidth(), src.getHeight(), null);
        g.dispose();
        return dst;
    }
}

getOverlayedImage()のsrcにlayを重ね合わせます。
layの中で、alphaColorに指定した色が透明になって透けます。

自信ねーんですよ、コレ


実際コレで一応実現しましたがね、歯車の再開発してるようにしか思えないんですよ。
もっと楽にできる素晴らしいやりかた、誰か教えてください。

※追記
改良?しました

2011年8月11日木曜日

SeekBarとImageButtonのソースっす

ImageButtonでSeekBarの加減算


どうも。いち、プログラマのyu1rowです。
以前に書いた「SeekBarとImageButton」に、リンクで辿ってくる人が多いようです。
せっかく辿っていただいても、ソースがねーよってんじゃあ、ガッカリでしょうね。
そうでもないんかな?
というわけで、一応ソースコードを載っけておきましょう。
我流ですので、悪しからず。

public class MainActivity extends Activity {

    /** アクティビティが作成される初回に呼び出されます。 */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main_activity);

        // 加算ボタンのイベント
        ((ImageButton) findViewById(R.id.btnAdd)).setOnClickListener(new OnClickListener() {
            @Override public void onClick(View v) { increment(R.id.sbDisp, 1); }
        });

        // 減算ボタンのイベント
        ((ImageButton) findViewById(R.id.btnSub)).setOnClickListener(new OnClickListener() {
            @Override public void onClick(View v) { increment(R.id.sbDisp, -1); }
        });
    }

    /**
     * シークバーの値を増減します。
     * @param id シークバーの ID
     * @param diff 増減する値 (マイナス値で減算)
     */
    private void increment(int id, int diff) {
        SeekBar bar = (SeekBar) findViewById(id);
        bar.incrementProgressBy(diff);
        bar.incrementSecondaryProgressBy(diff);
    }

}

上記のコードでは、「btnAdd」、「btnSub」というIDのボタンで、「sbDisp」というIDのシークバーの値を増減します。
実際のSeekBarの値を表示してぇよ!って人は、シークバーのsetOnSeekBarChangeListenerでリスナーを登録して、ラベルに値を表示する等してみましょう。
以上ですっ!

JSpinnerのちょっと困った話

JSpinnerを使おうと思いまして


Java+Swingのお仕事が続く毎日、yu1rowです。
スピナーってありますよね。
「増やす」ボタンと「減らす」ボタンが付いてるテキストボックス、みたいな。
数値入力ができるモードで、ちょっと困ったんで、その話。

止まっちゃうんですが!?


50~200まで入力できて、「増やす」と「減らす」ボタンを押すと、25ずつ増えたり減ったりするスピナーを作りました。
「↑」とか「↓」のキーボード入力でも増やしたり減らしたりできます。

ところが。

キーボードからも入力できるんで、「199」とかも入力できるんですね。
でも、「199」の状態で「増やす」ボタンを押しても増えてくれないんですよ。
199+25=224なんで、上限の「200」を超えちゃうからなんでしょうけど…。
こっちとしては、「200」になって欲しいわけです。

反対に、「55」の状態から「減らす」ボタンを押しても同じです。
「50」になって欲しいなぁ…と。

結局どうしたのか?



 ボタンを押したときのイベントと、テキストボックスでキー入力したときのイベントを捕まえて対処しました。
JSpinnerの中には以下のものが入っています
  • JButton (2個)
  • SpinnerModel (1個)
さらに、SpinnerModelの中には以下のものが入っています
  • JFormattedTextField (1個))
この2個のJButtonと1個のJFormattedTextFieldのイベントを捕まえちゃいます。
2個のJButtonにはそれぞれ以下の名前がついています
  • 「Spinner.nextButton」
  • 「Spinner.previousButton」
フレームに"spinner"という名前のJSpinnerがあるとする場合、以下のコードで2個のボタンにイベントを登録できます。
※コード中の「setMinimumValue()」と「setMaximumValue()」というメソッドは後述

for (Component c : spinner.getComponents()) {
    if ("Spinner.nextButton".equals(c.getName())) {
        // 次ボタンのイベント登録
        ((JButton) c).addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                setMinimumValue();
            }
        });
    } else if ("Spinner.previousButton".equals(c.getName())) {
        // 前ボタンのイベント登録
        ((JButton) c).addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                setMaximumValue();
            }
        });
    }
}

イベントから呼び出す「setMinimumValue()」と「setMaximumValue()」、そこから使用される「isSpinnerNumberModel()」は以下のように記述します。

/**
 * スピナーのモデルが {@link SpinnerNumberModel} かどうかを取得します。
 * @return スピナーのモデルが {@code SpinnerNumberModel} の場合 {@code true} 、
 * そうでない場合 {@code false}
 */
private boolean isSpinnerNumberModel() {
    SpinnerModel model = spinner.getModel();
    // modelがnull
    if (model == null) { return false; }
    // 数値モデル以外
    if (model instanceof SpinnerNumberModel == false) { return false; }
    return true;
}

/**
 * 最大値を設定します。
 * <p>
 * このメソッドは以下のどれかの条件に当てはまる場合は何も行いません。
 * <ul>
 * <li>スピナーのモデルが {@link SpinnerNumberModel} ではない</li>
 * <li>既に最大値がセットされている</li>
 * <li>次にセットすべき値 ({@link JSpinner#getNextValue()}) が {@code null} ではない</li>
 * </ul>
 * 以上の条件以外の場合、最大値 ({@link SpinnerNumberModel#getMaximum()}) が設定されます。
 * </p>
 */
private void setMinimumValue() {
    if (!isSpinnerNumberModel()) { return; }
    // SpinnerNumberModelにキャスト
    SpinnerNumberModel model = (SpinnerNumberModel) spinner.getModel();
    // 既にMaximumであれば無視
    if (model.getValue().equals(model.getMaximum())) { return; }
    // 次にセットするべき値がある場合は無視
    if (spinner.getNextValue() != null) { return; }
    // 最大値をセット
    if (model.getMaximum() != null) { model.setValue(model.getMaximum()); }
}

/**
 * 最小値を設定します。
 * <p>
 * このメソッドは以下のどれかの条件に当てはまる場合は何も行いません。
 * <ul>
 * <li>スピナーのモデルが {@link SpinnerNumberModel} ではない</li>
 * <li>既に最小値がセットされている</li>
 * <li>次にセットすべき値 ({@link JSpinner#getPreviousValue()}) が {@code null} ではない</li>
 * </ul>
 * 以上の条件以外の場合、最小値 ({@link SpinnerNumberModel#getMinimum()}) が設定されます。
 * </p>
 */
private void setMaximumValue() {
    if (!isSpinnerNumberModel()) { return; }
    // SpinnerNumberModelにキャスト
    SpinnerNumberModel model = (SpinnerNumberModel) spinner.getModel();
    // 既にMinimumであれば無視
    if (model.getValue().equals(model.getMinimum())) { return; }
    // 次にセットするべき値がある場合は無視
    if (spinner.getPreviousValue() != null) { return; }
    // 最小値をセット
    if (model.getMinimum() != null) { model.setValue(model.getMinimum()); }
}

キーボードの「↑」と「↓」キーのイベントを捕まえて値を変更させる場合のイベントは、以下のように登録します。
※ここでも前述の「setMinimumValue()」、「setMaximumValue()」、「isSpinnerNumberModel()」というメソッドを使用しています

// SpinnerNumberModelでない場合はイベント登録しない
if (isSpinnerNumberModel()) {
    // JFormattedTextFieldを取得
    JFormattedTextField textField 
        = (JFormattedTextField) spinner.getEditor().getComponents()[0];
    // keyPressedイベントの登録
    textField.addKeyListener(new KeyAdapter() {
        @Override
        public void keyPressed(KeyEvent e) {
            switch (e.getKeyCode()) {
                case KeyEvent.VK_UP:
                    setMinimumValue();
                    break;
                case KeyEvent.VK_DOWN:
                    setMaximumValue();
                    break;
            }
        }
    });
}

一応、これで目的の動作をしているようです。
※ご使用に際しては、自己の責任と計画を以てお願いします

これでよかったんかいな?


上記のようなモノを実装したカスタムしたコンポーネントを作って、今回はやり過ごしました。
ホンマはプロパティーひとつで出来ちゃうような、スマートなやり方があるのかもしれません。
今回はそれが見つけられなかったので、こんな強引なやり方で対処することになりました。
もっとスマートなやり方、募集中です。

2011年8月9日火曜日

JComponentのvisibleChanged?

色々忙しくて全く更新できていません


本業の方では仕事が変更になり、C#からJavaへ使用言語が変わりました。だからというわけでは...あるんですが、備忘録的にメモを残します。

Swingのお仕事


今回のお仕事では、Swingをメインに据えて作業していくことになりました。
そこで戸惑ったのが、C#ではVisibleプロパティを使って、ボタンなど、画面のモノの表示を消したり出したりしていました。
そしたら、Control.VisibleChangedなんてイベントが走りましてね、これを捕まえることができたんですが...

JavaのSwingコンポーネントでは?どうすんの?

何はともあれソースっす


結論から言うと、java.awt.event.ComponentListenerを追加すりゃなんとかなりました。
例えばボタンを作りましてね、そのVisibleを監視するのは以下のように

private void initComponents() {
    JButton button = new JButton("ボタンでっせ");
    button.addComponentListener(new ComponentAdapter() {
        @Override
        public void componentShown(final ComponentEvent e) {
            buttonVisibleChange(true);
        }

        @Override
        public void componentHidden(final ComponentEvent e) {
            buttonVisibleChange(false);
        }
    });
    add(button);
}

private void buttonVisibleChange(boolean visible) {
    System.out.println(visible);
}

  • componentShown() が表示されるとき
  • componentHidden() が消えるとき

見たまんまですね(^ー^

とにかく、C#とはちょっと勝手が違って悪戦苦闘気味ですが、こんな備忘録が増えていくかもしれません。