思索の日々、音との生活。

大学図書館パートタイマー(2年目)の広くて浅い思索の日々、音との生活。

VB.netで文字を正確な位置に描く。

.net Frameworkで文字列を描写しようと思ったら、Graphics.DrawStringを使おうと思うわけですが、このメソッドは、座標を指定すると、その座標を左上隅に文字列を描写してくれます。さらに、一番目の文字の左側に空白を入れてくれます。

まあ、通常の文字列を描画する際にはこれで問題ないのかもしれませんが、ある程度正確な位置に描写したいとなると、結構やっかいです。

ここでは、指定座標をベースラインの左端として文字列を描画する方法を解説します。

※超長文なので、面倒な方は一番下の関数をコピペして使ってください。


では、まず最初に用語の定義。

◇フォントで使われる単位

あるフォントの左上端から右下端までを「emスクウェア(.netのヘルプではem四角形)」と呼びます。
ここで、下端というのはベースラインでなく、あらゆる文字をひっくるめた下端、たとえばpの字の下端です。
そして、emスクウェアの縦の長さを「1em」とします。
DrawStringをはじめ、多くのアプリケーションでフォントサイズを指定したときは、この1emをどの大きさで描画するかを指定しています。
※そうとも限らないようです。現在調査中。
あるいは、emスクウェアに収まらない文字があるのか。


フォントファイルでは、抽象的(論理的ともいう)な単位で文字の形を記述しています。
この単位はフォント単位(font unitまたはfunit)、デザイン単位、フォントデザイン単位などと呼ばれます。
1emが何フォント単位かは、フォントによって異なります。32の倍数ならいくつでもOKのようです。

上端からベースラインまでの長さをアセント、ベースラインから下端までの長さをディセントといいます。
アセント/ディセントは当然、フォント単位で示されます。

◇ポイントについて

フォントサイズは普通「ポイント(pointまたはpt)」という単位で指定されます。
ポイントはmmやインチと同じような(ピクセルとは違う)長さの単位です。
日本では1ポイント=0.35mmとされてるようですが、コンピュータ上では1ポイント=1/72インチ(逆に言えば1インチ=72ポイント)として扱うのが一般的です。

一方、ディスプレイはピクセル(ドット)単位で描画を行います。
ディスプレイのサイズ、解像度によって1ピクセルがどれだけの大きさになるかは変わるわけですが、それも面倒なので(?)Windowsでは96dpi(1インチ=96ピクセル)Machintoshでは72dpi(1インチ=72ピクセル)としているようです。
(ただし、WinXPなどは133dpiなどより高いdpiに設定することが可能です。Macは残念ながら知らない。)

よってポイントとピクセルとは以下の式で変換できます(Windowsの場合)

 ピクセル = ポイント * (96 / 72)
 ポイント = ピクセル * (72 / 96)
(Macだと96の代わりに72を入れるので、そもそも変換の必要なし。)

なお、.netではGraphics.DpiXおよびGraphics.DpiYでGraphicsオブジェクトのdpiが得られます。

◇上端からベースラインまでの長さを得る

DrawStringでは指定した座標を文字列の左上端として描画するので、ベースラインの位置で描画するためには、上端からベースラインまでの長さを知る必要があります。

が、上端からベースラインの長さ=アセントはフォント単位でしか得られませんので、そのままでは使えません。
そこで、1emとアセントの比がわかれば、フォントサイズ(1emと対応)を掛ければアセントの長さがわかります。

アセントの値を得るには、FontFamily.GetCellAscentを使います(Fontインスタンスを作った場合、Font.FontFamily.GetCellAscentでOK)。

1emの長さをフォント単位で得るにはFontFamily.GetEmHeightを使います。
(GetCellAscent、GetEmHeightには引数としてフォントのスタイル情報−標準か、太字か斜体か...−を指定します)

フォントサイズはFont.Sizeで得られますが、ここで得られる値はFont.Unitに設定された単位です。ポイント単位で得られるFont.SizeInPointsもあります。

よって、あるFontインスタンス f のアセントの長さは以下の式で得られます。

 AscentLength = f.Size * (f.FontFamily.GetCellAscent( _
              FontStyle.Regular) / _
              f.FontFamily.GetEmHeight( _
              FontStyle.Regular)

ただしこれは、フォントサイズの単位を考慮していないので、得られる値はf.Unitに設定された単位です。

上でピクセルとポイントの変換式を作ったので、フォントサイズをポイントで得て、ピクセルでアセントの長さを返す式を作ってみましょう。

 AscentLengthInPixel = (f.SizeInPoints * (96/ 72)) * _
            (f.FontFamily.GetCellAscent( _
             FontStyle.Regular) / _
             f.FontFamily.GetEmHeight( _
             FontStyle.Regular)

あとはDrawStringを使うときにY座標からこの値を引けば、指定したY座標をベースラインに描画してくれます。

◇右にずれる

こうして描画してみると縦位置はいいのですが、横位置は指定座標より少し右にずれて描画されてしまいます。
普通に文字列を描画するときは、左端ぎりぎりより見やすくていいのかもしれませんが、正確な位置に描画したい場合には余計なお世話・・・。

この「ずれ」の量について、ヘルプを見ても(あんまり見てない)見つからなかったので、乱暴にも実測しました。

その結果、ずれの量は1emのおおよそ0.17倍とわかったので、以下の式でずれの量を得ます。

 ずれ = Font.Size * 0.17

例によってポイント−ピクセル変換を入れます。

 ずれ = Font.SizeInPoints * (96 / 72) * 0.17

で、DrawStringを使うときにX座標からこの値を引けば、指定したX座標ぎりぎりに描画してくれます。

◇関数を作る

はぁ(疲れた)。
これで、やっと関数が作れます。

以下は、文字列s、ブラシbrush、描画するGraphicsインスタンスg、フォントf、描画位置ptを引数に取り、描画位置をベースライン・左端ぎりぎりにして文字列を描画するサププロシージャです。

ピクセル−ポイント変換は面倒なので定数を作ってます。ほんとはGraphics.dpiXなどを使って、縦横別々に計算すべきですが、面倒なので省略。


'指定座標をベースライン・左端として文字列を描画します。
Sub DrawStringB(ByVal s As String, ByVal font As Font, _
       ByVal brush As Brush, ByVal pt As Point, _
       ByVal g As Graphics)
  Dim ascent As Integer
  Const pixelperpoint = 96 / 72

  ascent = (font.Font.SizeInPoints * pixelperpoint) * _
       ((font.FontFamily.GetCellAscent( _
        FontStyle.Regular) / _
        font.FontFamily.GetEmHeight( _
        FontStyle.Regular))
  g.DrawString(s, font, brush, _
         New Point(pt.X - (font.SizeInPoints * _
         pixelperpoint * 0.17), pt.Y - ascent))
End Function

はぁ。フォントは単位がいっぱいで頭がこんがらがります。
フォントについてもっと詳しく知りたい方はこちらへ。
あと、FontFamilyクラスのメンバを見るといろいろあるみたいです。
あー、疲れた。

追記:
参考URL
MSDN - フォント メトリックを取得する
TrueTypeについて - Microsoft(en)
Wikipedia(en) - x-height
アセント/ディセントのほか、x-height、cap-heightという値もあるらしい。
これらによってフォントサイズ=1emとは限らないらしい?



さらに追記:
.net Framework 3.0に実装されたWPF関係(?)のクラスSystem.Windows.Media.GlyphTypefaceを使うとx-heightやcap-heightなど細かなフォント情報がわかる模様。現在、いろいろテスト中。

ソフトテルミン公開。

何年か前にほぼ完成してたテルミンのシミュレーションソフト「ソフトテルミン」がようやく公開できるようになりました。
で、Vectorさんにアップして数日したら、新着ソフトレビューで取り上げて頂けるとのこと。窓の杜でソフトレビューを書いて頂いたことは過去に2度(コレコレ)あったが、Vectorは初めて。
7月14日にレビューが掲載されるというので、見に行ったところ・・・

vector

(2007/07/17のVectorトップページ。下の方に注目)
おお!トップページにソフトテルミンが!(感動)

で、レビューはこちら
作者の私よりよほどよく解説してくれてます。さすがプロ。

毎度のことながらフリーウェアなので、紹介されようが、DL数が増えようが一銭の得のもなりゃしませんが、人様に評価されるのは嬉しいモノです。

追記:
窓の杜でも紹介していただきました。
世界最古の電子楽器“テルミン”をパソコン上で演奏できる「ソフトテルミン」

VB.netでカーソルを変更する。

ずっと前にVisual Basic .netで作ったソフトで、カーソルを変えようと思ったら、なぜかカラーのカーソルが白黒2色でしか表示されなかった。
きっとこれは.net Frameworkのバグだと思って、バージョンアップを待ったのだが、Visual Basic 2005 Express Edition(無償で商用利用可。すばらしい。)に添付のヘルプで「Cursorクラスについて」を見たところ・・・

Cursor クラスでは、アニメーション カーソル (.ani ファイル)、またはカラーのカーソルはサポートされていません。

おいっ!仕様にするなよっ!

・・・ということで、Microsoftには期待できそうもないので、色々と調べてみたら、Win32API使えばできそう、というのがわかり、ためしてみたら行けました。以下、カーソルのファイル名を指定して、Cursorクラスのインスタンスを返す関数(適当に例外処理とか追加してください)。


Declare Function LoadImage Lib "user32" _
  Alias "LoadImageA" (ByVal hInst As Integer, _
  ByVal lpsz As String, ByVal un1 As Integer, _
  ByVal n1 As Integer, ByVal n2 As Integer, _
  ByVal un2 As Integer) As Integer

Function LoadCursor(ByVal CursorFilePath As String)
  Dim ip As New System.IntPtr( _
    LoadImage(vbNullString, CursorFilePath, _
    2, 0, 0, &H10))

  Return New Cursor(ip)

End Sub


これをコピペして、Me.Cursor = LoadCursor("C:\test.cur")のように使って下さい(なんて投げやりな解説)。

しかし、Win32APIでできることは、.net Frameworkでもなんらかのかたちで実現できて欲しいです・・・。

なお、今回LoadImageの使い方について、WinAPI Database for VB ProgrammerLoadImageの項を参照しました。VB.NETでの宣言まで載ってて、えらい便利です。

「ブツ撮り」初挑戦。

写真の世界では、商品等の写真を撮る事を「ブツ撮り」というそうです。

そうそう旅行なんかにもいけない身分としては、カメラがあっても猫とモノくらいしか撮るものがないので、モノを綺麗に撮りたいと考えたわけで。
とはいえ、本格的な撮影には、いろいろと機材が要り、アマチュア向けのセットもあるとはいえ、安くて5000円の出費は痛い。

そんななか、できるだけお金をかけず、一般家庭でできるやりかたを紹介しているところがあった。

ITmedia PCUPdate:第13回 小物と撮影セッティングの関係 (1/2)

これで、撮ってみたわけだが、カメラのモニタで見てる分にはよくても、PCにデータを移すと、色の感じや明るさの印象も違うし、よくみるとピントが少しずれてる時もある。

そこで、PCでリアルタイムに確認しながら撮れないものかと、いろいろ調べてたら、カメラ(Canon EOS Kiss Digital N)に標準で付いてるEOS Utilityのリモート撮影機能で実現できました。

PC上からシャッタースピードや露出、ISO感度などをコントロールしつつ、ボタンクリックで撮影、1〜2秒でPC画面で確認。めっちゃ便利です。

写真ど素人の私には適切な設定を考えて撮るなんて芸当はできないので、パラメータ変えては撮る、変えては撮るで、1つの構図を撮るのに20枚ほど撮ってます。いやでも、これは楽しい。

そんなこんなで、撮影場所はこんな感じに。

studio

こたつ+ポスター+蛍光灯スタンド+ミニ三脚にカメラとPC(モデル?の下には高さを稼ぐため、ほぼ日手帳の立派な箱を置いてます)。

それで、できた写真がコレ。

frog
(※重いです)

いままでのなかで、一番綺麗かも。
せっかくなのでトリミングして壁紙に。
うーん、かわいい。

wall

しばらくはブツ撮りの日々が続きそうです。

Amazonで本を探しやすい3つの理由。

前項で、Amazonの商品紹介ページから、図書館の蔵書を探す方法を紹介したが、考えてみると、これはすなわち、Amazonが本を探す際のポータルになっていることを示している。

図書館はWebサイトやOPACを作り、本(を含む情報)を探すためのポータル作りをやっているところもあるが、それをさしおいてAmazonがその役割を果たしているわけだ。

私も普段、本を探し、入手する課程でAmazonを利用する機会は多い。そこで、Amazonで本を探しやすい3つの理由を考えてみた。

1.表紙画像の存在
ネットで本を探す場合、立ち読みができない、という点が一番の障害となる。そんな中、表紙だけでも画像が見られることは、その本の雰囲気をつかむ上で、わずかなりとも手助けとなる。図書館などがつくるポータルでは、こうした画像は圧倒的に少ない。
Amazonでは表紙以外の画像が見られる場合もあり、さらに大きな手助けとなる。
画像の存在は、単に合理的な要求から必要とされるものだけではなく、ユーザーの「感情」を左右するものとしても重要である。同じ情報でも、文字だけのものより、画像や視覚的デザインを含んだものの方が見ていて楽しく、「ついつい」使い続けてしまうものだ。

2.ユーザからの暗黙的情報の利用

Amazonを見ていて驚くのが「おすすめ商品」だ。しばしば、自分の持っている(一見、無関係な)商品をすすめられる事から考えて、おすすめ商品の選出はかなり的確である。
これらは、推察するに、1人のユーザが購入・チェック・ウィッシュリストへ登録した商品のデータと、すべてのユーザについてそれらを集積したデータをつきあわせて、相関性の高いものを選出しているのであろう。
これは、Amazonのみが持っているデータであり、他サイトでAmazonほどのデータを集積するのは容易ではない。

3.ユーザからの明示的情報

実は、個人的には、これが一番本選びに強い影響を及ぼすと考えている。
Amazonでは、個々の商品ページに、その商品に対するユーザからのレビューが掲載されている。また、「リストマニア」として、ユーザが特定のテーマ等を設定し、ユーザが選んだ商品を、レビュー付きで紹介することができる。当然リストマニアと個々の商品ページは相互にリンクしている。
MARCの内容紹介などとは違う、率直で主観的な評価。また、本の主題ではなく、特定の目的や、趣向から選んだリスト。これこそが、本を選ぶ時にもっとも重要な情報源となる。
(もっともレビューの質は玉石混淆であるから、その点は留意しなければならない。また、このことが、公的なプロジェクトに導入できない理由でもあるのだろう。)

見てみると、Amazonで本が探しやすいのは、洗練されたシステムに加え、長年大手ショッピングサイトとして蓄積してきたデータが大きな強みになっていると考えられる。
しかし、それを言うなら、図書館だって長年情報を提供してきたノウハウも顧客(利用者)もあるのだから、あきらめるのは早い。利用者に貸し出した本の感想と評価を書かせ(プレゼントでもつければよかろう)、蓄積・公開するだけでもずいぶん違うだろうし、利用者におすすめの本を選ばせるのもおもしろい。大学図書館なら、各分野の名著を教員にきいてまわるのも効果があるだろう。

Amazonに追いつけ追い抜けでいろいろやってみてほしいものだ。

追記:
ところで、Amazon等営利団体のサイト以外で、本を探す場合に、おもしろいのは国立情報学研究所(NII)のWebcat PlusとNPO法人・連想出版の想-IMAGINE Book Search(あるいはWebマガジン「風」から)である。どちらもキーワードのシソーラスを使った「連想検索」が大きな売りである。
前者は、大学図書館の蔵書情報と連動しているので、Amazonに無いような少し古い文献があり、全国の大学図書館の蔵書状況も確認できる。
後者は、もともと新書専門の「新書マップ」をやってたとこが、新書マップ・Webcat Plus・Wikipediaなどの統合検索システムを作ったらしい。新書マップは、新書だけに限定しているが、表紙だけでなく「背」の画像を持っていたり、インターフェイスが独特だったりと、なかなかおもしろい。ちょっと使ってみただけだが、Webcat Plusとの連携は効果的な感じだ。