>

2011年7月24日日曜日

WPFで独自に用意したフォントファイルを使う方法

まず次のようにプロジェクトにフォントファイルを追加する。下記例ではfontsというフォルダを作りその下に7barSP.otfというフォントファイルを追加している。


次にxamlでFontFamily属性に次のようなスクリプトを記述する。



#の後にはフォント名を記述する。この例では"7barSP"というフォント名になっている。フォント名の調べ方は少なくともWindows7ではフォントファイルをダブルクリックすることで開くWindowsフォントビューアーで確認することができる「フォント名」のところにある文字列を使用すればよいとものと思われる。


2011年2月23日水曜日

C#でEXIFのDateTimeOriginalのみを高速に取得する

C#ではJPEG画像のEXIFを読み込むための方法としてPropertyItemsを使用する方法が用意されているがこのライブラリは汎用的なために撮影日時といったような一つの値だけが欲しい時などは非常に遅くて不便。

なのでC#でJPEGの撮影日時(DateTimeOriginal)のみを高速に取得するコードを書いた。
ただし次の制約があります。
  • 簡単のため撮影日時はJPEGファイルの先頭から1000バイト以内にあることを前提としている
  • 例外処理は行っていない
  • 基本的に動くことだけ確認して細かいところは見直していない

public static string ReadExifDateTime(string path)
{
    byte[] jpegBytes = new byte[1000];
    using (BinaryReader reader = new BinaryReader(File.OpenRead(path)))
    {
        reader.Read(jpegBytes, 0, 1000);
    }

    bool isLittleEndian = false;
    if (jpegBytes[12] == 'I')
    {
        isLittleEndian = true;
    }

    int ifd0Offset = ReadInt(jpegBytes, 16, 4, isLittleEndian) + 12;
    int ifd0DirCount = ReadInt(jpegBytes, ifd0Offset, 2, isLittleEndian);

    int subIfdOffset = 0;
    for (int i = 0; i < ifd0DirCount; i++)
    {
        int ifd0Tag = ReadInt(jpegBytes, ifd0Offset + 2 + 12 * i, 2, isLittleEndian);
        if (ifd0Tag == 0x8769)
        {
            subIfdOffset = ReadInt(jpegBytes, ifd0Offset + 2 + 12 * i + 8, 4, isLittleEndian) + 12;
            break;
        }
    }
    if (subIfdOffset == 0)
    {
        return null;
    }

    int subIfdDirCount = ReadInt(jpegBytes, subIfdOffset, 2, isLittleEndian);
    int dateTimeOffset = 0;
    for (int i = 0; i < subIfdDirCount; i++)
    {
        int ifd0Tag = ReadInt(jpegBytes, subIfdOffset + 2 + 12 * i, 2, isLittleEndian);
        if (ifd0Tag == 0x9003)
        {
            dateTimeOffset = ReadInt(jpegBytes, subIfdOffset + 2 + 12 * i + 8, 4, isLittleEndian) + 12;
            break;
        }
    }
    if (dateTimeOffset == 0)
    {
        return null;
    }

    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < 20; i++)
    {
        sb.Append((char)jpegBytes[dateTimeOffset + i]);
    }

    return sb.ToString();
}

private static int ReadInt(byte[] bytes, int offset, int count, bool isLittleEndian)
{
    int retVal = 0;

    if (isLittleEndian)
    {
        for (int i = offset + count - 1; i >= offset; i--)
        {
            retVal = (retVal << 8) + bytes[i];
        }
    }
    else
    {
        for (int i = offset; i < offset + count; i++)
        {
            retVal = (retVal << 8) + bytes[i];
        }
    }

    return retVal;
}

上記コードをPropertyItemsを使って読み込む場合と速度の比較のためにHDD上のJPEG(1,590,029 バイト)の撮影日時を100回読み込むのにかかる時間を計測した結果

上記コードの場合: 15ms
PropertyItemsを使用した場合: 29395ms

とPropertyItemsを使用した場合に比べて2000倍くらい速いことが確認できる。EXIFの解析って大変なんですね。 なお、PropertyItemsを使用して取得するコードとしては以下を使用した。

public static string ReadExifDateTimeUsingPropertyItems(string path)
{
    //画像を読み込む
    System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(path);

    for (int i = 0; i < bmp.PropertyItems.Length; i++)
    {
        System.Drawing.Imaging.PropertyItem pi = bmp.PropertyItems[i];
        if (pi.Id == 36867 && pi.Type == 2)
        {
            return Encoding.ASCII.GetString(pi.Value);
            break;
        }
    }
    return null;
}

2010年12月15日水曜日

SyntaxHighlighter覚え書き

ブログにソースコードを書くときはSyntaxHighlighterが綺麗にハイライトして表示してくれそうなのでその使い方の覚え書き。

ブログのテンプレートなどをいじってSyntaxHighlighterのjavascriptを読み込むよう編集する。

ブログの記事を書くときはソースコードを次のように囲む。

###MY_CODE###

brush名はコードの種類に合わせて適切なのを選択する。
利用可能なbrushはこちらを参照のこと。

2010年11月10日水曜日

if 文の中括弧とコメントの書き方

if 文の中括弧は下の例のように条件式と同じ行には書かずに改行してから書く。

if else ブロック全体に対するコメントは最初のif 条件式の前の行に書き、条件文の意味に関するコメントは条件式と同じ行の右となりに書くことで二つの種類のコメントを区別しやすくすると全体的に見やすくなる気がするので。
//if elseブロック全体に対するコメントはここに書く。
if (index == -1)//この条件式の意味に対するコメントはここに書く。
{
    ...;
}
else if (index == fragments.Count)//この条件式の意味に対するコメントはここに書く。
{
    ...;
}
else//この条件式の意味に対するコメントはここに書く。
{

}

2010年10月22日金曜日

CollabNetのsubversion for Win32bitをうまくインストールできない件

CollabNetのsubversionをインストールすると自動的に C:\csvn\launch.html が開かれるのだけどいつまでたってもConsole StatusがNot Readyのままで何も始まらない件で僕のケースでの対応をまとめる。

まず、Serviceが動いてないのかなと思って、登録されたCollabNet Subversion Serverを手動で開始するとエラーで
httpd.exe: Could not open configuration file C:/csvn/data/conf/httpd.conf:
みたいなメッセージが出るのは問題なし。httpd.confは本来subversionの設定UIで設定作業を行うと自動生成されるものらしい。

設定UI(CollabNet Subversion Edge)が動かないのが本質的な問題だが、この理由は環境変数JAVA_HOMEで指し示されているJavaがおかしくて CollabNet Subversion Edge Service のインストールに失敗していたことが原因だった。
設定UIのインストールは %JAVA_HOME%\bin\java.exe を使用して行われるが試しに僕の環境でコマンドプロンプトからこのJavaを実行すると

Error occurred during initialization of VM
java/lang/NoClassDefFoundError: java/lang/Object

というエラーが出てしまい、設定UIの起動に失敗していた。コントロールパネルからJavaを一旦アンインストールしてからSun(Oracle?)のサイトからJRE6の最新版をインストールしてやることで解決した。なお、JAVA_HOMEが示すjavaは1.6以上じゃないと動かないので注意。
こうしてJAVA_HOMEの下のjavaを実行するとちゃんとオプションの説明みたいなのが出るような環境にしてから

C:\csvn\svcwrapper\bat\installService.bat

を実行するとCollabNet Subversion Edgeが正常にインストールされる。手動でインストールしたらServiceの画面からCollabNet Subversion Edgeを手動で開始すると元のC:\csvn\launch.htmlにてConsole StatusがReadyになった。
Readyになるまで結構時間がかかるので注意。

参考
http://subversion.open.collab.net/ds/viewMessage.do?dsForumId=3&dsMessageId=381180

2010年9月28日火曜日

JavaからJDBCを使用してOracleにSQLを投げるときのTimezoneの注意点

有名な話ではあるとは思うがJavaのOracle JDBCドライバはタイムゾーン情報を持てない。
※JDBC全般の話なのかOracle特有の話なのかは未確認だがAPI仕様を見る限りJDBC全般の話かも。

JavaからJDBCを使用してOracleにSQLを投げるときにSQLのWHERE句に日時が入っているときはタイムゾーンに関して注意が必要。

PreparedStatementにsetTimeStampやsetDateしてSQLを実行する際、JDBCは引数で渡すDateのエポック秒でSQLを実行するのではなく、引数で渡したDataをマシンのデフォルトタイムゾーン
で出力した(タイムゾーンを伴わない)日時でSQLを実行する。DBを受け取った日時をDBのタイムゾーン設定に基づいて解釈する。

なので特に対策をしないで実行すると次のような問題が起こる。
  • クライアントマシンのタイムゾーンとDB(Oracle)のタイムゾーンが異なると意図する結果が戻らない。
  • クライアントマシンのタイムゾーンが一致していたとしてもJavaで勝手にサマータイムとかが考慮されて一時間ずれた結果が戻ったりする。

特に二つ目の問題は、プログラムをリリースした時期が冬で正常に動作していたのにサマータイムの季節に突然発症したりして厄介。

以上を踏まえるとクライアントのJavaプログラムでSQLを呼び出すときには次の処理を忘れずに行う必要がある。
  1. Javaプログラム中でDBのタイムゾーンをGMT-HH形式で保持する。
    Timezone oracleTZ = TimeZone.getTimeZone("GMT-5");
  2. そのタイムゾーンをデフォルトタイムゾーンとして指定する。
    TimeZone.setDefault(oracleTZ);

デフォルトにすると不都合な場合はPreparedStatementで時刻を扱う都度設定。
PreparedStatement ps = ..... ;
ps.setTimestamp( 1, t, Calendar.getInstance(oracleTZ) );


まとめると
  • OracleJDBC(OracleのみならずJDBC全般の可能性あり)はタイムゾーン情報を持たないためJavaプログラム中にデータベースのタイムゾーン設定する必要がある。
  • Javaプログラムでタイムゾーンを設定する場合は"EST"みたいな三文字略称を使うと勝手にサマータイムとかが考慮されることがあるので"GMT-5"のような形式で設定するのがよい。

捕捉
もっとも大抵の場合はWHERE句でバインド変数の日時を使うのを避けてOracleのSYSDATEなどDB内で完結した表現で日時を設定するのが無難と言えば無難。

2010年9月14日火曜日

CloudFrontによる一回目ダウンロードの速度調査

CloudFrontは基本的にはキャッシュサーバなので一回目のDLでは速度向上は期待できないのではないかという疑問があったので実験してみた。

実験は1.5MBのデータファイルをAmazonS3のBucketから直接DL(RESTのGET API )するのに要する時間と同じファイルをCloudFrontを使用してDLするのに要する時間を比較した。
実験では各イテレーションで実験用ファイルをその都度新規ULしてから時間を計測しているのでCloudFrontからのDLは常に一回目である。実験結果を以下に示す。

【実験条件】
実験日時:2010年4月26日
AmazonS3のBucket置き場所:US
アクセス元:日本
ファイルサイズ;1,722,874バイト
イテレーション数:100回

【結果】
Bucket直接アクセスの平均DL時間:11.7秒
CloudFront経由アクセスの平均DL時間:4.3秒


結果を見てのとおり、CloudFrontはたとえそのコンテンツへの初めてのアクセスであってもDLに要する時間を半分以下に短縮していることが分かった。単なるキャッシュサーバ以上の役割を果たしているようである。