2015年4月
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30    
無料ブログはココログ

« 2012年2月 | トップページ | 2012年5月 »

2012年3月の3件の記事

2012年3月22日 (木)

画像を貼り付ける(2) - まじめに draw_bitmap

Device Context に画像を貼り付ける方法で、以下のコードでとりあえず動きます。

# encoding: UTF-8

require 'wx'

class MyApp < Wx::App
  def on_init
    @main_frame = FrameMain.new
    @main_frame.show
  end
end

class FrameMain < Wx::Frame
  def initialize( title = "" )
    super( nil, -1, title, :size => [450,500] )
    sizer_top = Wx::BoxSizer.new( Wx::VERTICAL )
    set_sizer( sizer_top )
    sizer_top.add( make_button(), 0, Wx::ALIGN_CENTER_HORIZONTAL )
    sizer_top.add( @panel = MyPanel.new( self ), 1, Wx::EXPAND )
  end

  def make_button
    button = Wx::Button.new(self, -1, "Browse image file" )
    evt_button( button ) { |e| browse_file( e ) }
    button
  end

  def browse_file( evt )
    dlg = Wx::FileDialog.new( self, 'file 選択' )
    if ( dlg.show_modal == Wx::ID_OK )
      @panel.load_image( dlg.get_path )
    end
  end
end

class MyPanel < Wx::Panel
  def load_image( file_name )
    img = Wx::Image.new( file_name )
    img = img.rescale( img.width/2, img.height/2 )
    bitmap = img.convert_to_bitmap
    paint do |dc|
      dc.clear
      dc.draw_bitmap( bitmap, 0, 0, true )
    end
  end
end

MyApp.new.main_loop

前々回のコードで、Wx::StaticBitmap() を使っていたところを DC#draw_bitmap() で描画するように書き換えました。draw_bitmap() の前に、DC#clear で画面を一旦消すようにしています。例によって、困った時の"wxRubyでGUIプログラミング" 頼り、で、Wx::Window#paint の使い方をカンニングしました。

Wx::Window#paint のドキュメント を参照すると、evt_paint {...} の中で呼ばれると、PaintDC を、それ以外の文脈で呼ばれると、ClientDC をブロック引数として拾い上げるそうなので、このコードであれば、ClientDC に描画しているのでしょう。

上のコードを実行すると、ある画像を表示してから、もう一度 button をクリックして別のファイルを選ぶと、ちゃんと前の画像を消去して新しく選んだ画像を表示してくれます。

ところがどっこい、これで一旦ウィンドウを縮小し、再度拡大すると、欠けた部分の画像が再描画されません。これでは使えない!

あれこれ試して、この問題を解決したのが次のコードです。

# encoding: UTF-8

require 'wx'

class MyApp < Wx::App
  def on_init
    @main_frame = FrameMain.new
    @main_frame.show
  end
end

class FrameMain < Wx::Frame
  def initialize( title = "" )
    super( nil, -1, title, :size => [450,500] )

    sizer_top = Wx::BoxSizer.new( Wx::VERTICAL )
    set_sizer( sizer_top )
    sizer_top.add( make_button(), 0, Wx::ALIGN_CENTER_HORIZONTAL )
    sizer_top.add( @panel = MyPanel.new( self ), 1, Wx::EXPAND )
  end

  def make_button
    button = Wx::Button.new(self, -1, "Browse image file" )
    evt_button( button ) { |e| browse_file( e ) }
    button
  end

  def browse_file( evt )
    dlg = Wx::FileDialog.new( self, 'file 選択' )
    if ( dlg.show_modal == Wx::ID_OK )
      path = dlg.get_path
      @panel.load_image( path )
      set_title( path )
    end
  end
end

class MyPanel < Wx::Panel
  def initialize( *args )
    super
    evt_size { |evt| draw_image }
  end

  def load_image( file_name )
    @img ||= Struct.new( :image, :width, :height ).new
    image = Wx::Image.new( file_name )
    @img.image = image
    @img.width = image.width; @img.height = image.height
    draw_image
  end

  def draw_image
    return unless( @img )

    width = self.get_client_size.width
    height = self.get_client_size.height
    ratio = [ width.to_f/@img.width, height.to_f/@img.height ]
    new_scale = if ( ratio.first < ratio.last )
      [ width, (@img.height * ratio.first).to_i ]
    else
      [ (@img.width * ratio.last).to_i, height ]
    end
    bitmap = @img.image.copy.rescale( *new_scale ).convert_to_bitmap
    position = [ (width - new_scale.first)/2, (height - new_scale.last)/2 ]
    paint do |dc|
      dc.clear
      dc.draw_bitmap( bitmap, position.first, position.last, true )
    end
  end
end

MyApp.new.main_loop

まず、DC#draw_bitmap で描画するルーチンを MyPanel#draw_image としてくくりだしました。MyPanel#load_image から呼び出すのは当然として、MyPanel#initialize で evt_size{ } にも event handler として割り付けています。つまり、ウィンドウの拡大縮小に伴って、MyPanel のサイズが変わると evnet が発生するので、その度画像を際描画できます。

単に画像が欠けて再描画されるだけでもよいですが、せっかくなので、ウィンドウサイズに応じて画像そもののが拡大縮小されるようにしてみました。(やっと、当初の目標であった、任意の画像を任意に拡大縮小できるところまできました)

画像の拡大・縮小処理は、MyPanel#draw_image の中でやっていますが、要は、もともとの jpeg 画像の縦横 pixel 数と、現在の MyPanel の表示エリアサイズ( Wx::Window#get_client_sizeで取得しています ) を比較して、画面からはみ出さないような画像のサイズを求めています。pixel サイズは整数、拡大率は実数、Wx::Image#rescale() に与える引数は整数で、rescale() の実装が暗黙の型変換をしてくれないようなので、適宜 to_f, to_i で整数⇔実数変換をしています。

Device Context について、やっぱりまだまだよくわからないことが多いです。前々回で参考にしたサイトの"white wheelsのメモ" では、

「デバイスコンテキストはwx.PaintDC(window)で得られるのですが、
"Paintイベント"のときにしか取得できないので、onPaintイベントハンドラを作成します。」

とあったので、当初は、evt_size{ } ではなく、evt_paint{ } で draw_image を呼び出すようにしてみました。ところが、なんともうまくいかないのですね。それで evt_paint{ } を使わずに、evt_size{ } で実装してみたら成功したわけです。

( MyPanel#initialize 中の evt_size{ } を evt_paint{ |evt| draw_image } に変えてみてください。うまく動かなくて困ります)

"Paintイベント"でなくても Device Context は取得できているようですし・・・。 まあ、よくわからないながらも、いろいろと書いているうちに Device Context が理解できるようになることを期待しつつ、今日はここまでとします。

2012年3月21日 (水)

キーボード買いました

机が狭くなるのでフルキーボードは嫌いですが、小型のキーボードはこれまでいくつも買っています。

一番安いタイプですが、当然 HHK は買っています。

Pdkb210w02_s_2 [ PD-KB210W/U ]

ポインティングデバイス付の小型キーボードも暫く追求したので、IBM Space Saver Keyboard II も買いました。今でも有線で使う時は、これが総元締めといった感じです。

356c1d75ad37634d6cec9094eee20f66[ Space Saver Keyboard II ]

写真が見つかりませんでしたが、 Space Saver Keyboard II の前には、OKI の Mini keyboard II を使った時期もありましたが、 これはキータッチがよくありませんでしたね。

その後は無線キーボードが欲しくなり、タッチパッド付のサンワサプライのやつを買いました。悪くはなかったけれど、タッチパッドの分、結構面積を食ったのと、タッチパッドの移動量が小さくて、共用するマウスのカウントにあわせると、パッドでの移動が大変不便でした。

Skbwltp01sv_ma[ SKB-WLTP01SV ]

このあたりで、ポインティングデバイス一体型は諦めて、スライドパッドかマウスを別に繋いで使うようにしました。で、こんなやつにも手を出しました。横幅 22cm. しかも、bluetooth で同時に 9 台まで接続できるのは大変便利なので、これは今でも必要に応じて多用しています。

Main [ TK-FBP013 ]

でも、流石にサイズ的に常用は無理です。それで、つい最近まで常用していたのが、ロジクールの、これ。

22704[ Wireless Keyboard K340 ]

フルキーボードの割りに、許せる程度に小さくて、なおかつ、右シフトキーの幅が小さくないのが良いところです。右シフトキーの隣に上カーソルキーが割り込んでいるレイアウトだと、頻繁にタイプミスするので、そういうタイプのものはできるだけ避けたいのですが、右シフトキーの条件をつけるだけで、かなり選択肢が減ってしまうのが寂しいところです。

上の K340 で、かなり満足できていたのですが、一点だけ不満だったのが、キータッチが重いことです。K340 に限らず、PC ショップでいろんなキーボードを触ってみても、基本的にどれも重い。私の筆圧が低すぎるというのが正しいのでしょうが、サイズやレイアウトを別にしても、キーの重さで気に入るものはめったにありません。

先日出かけた先の PC ショップで、結局これを衝動買いしてしまいました。FILCO の Majestouch シリーズ。

本当は、これが欲しかったのですが

Fkbn91mcjb2_01[ FKBN91MC/JB2 ]

店頭にこれがなかったので、まぁ、実用上の問題はないやということで、買ったのはこれ。

Fkbn91mcnfb2_01[ FKBN91MC/NFB2 ]

選んだのは「青軸」です。青軸はクリック音がやかましいので、茶軸でもよかったのですが、茶軸よりも青軸の方が更にタッチが軽い。軽さに惚れて一万を超えるキーボードを買ってしまいました。

但し、ケーブルはえらく太くて固くて長いので、狭い机の上での取り回しが面倒です。速攻で分解して、ケーブルを巻き取り式の細いやつに取り替えました。最近はハンダ付けするのも、これくらいの工作のときだけになりました。これで、どうやら、このキーボードが常用になりそうです。

使い始めてみて思い出しました。昔気に入っていた、X68k のキーボードはこのくらいのキータッチでした。今から思えば、あれが一番使いやすかった。これくらい軽いキーボードが増えてくれればありがたいのですが、メンブレンスイッチでは無理なんでしょうねぇ。

そういえば、FILCO、このシリーズで無線キーボード作ってくれないでしょうか。技術的には難しくないと思うんですが。無線キーボードがないから、仕方なく他ので妥協しているわけで、青軸の無線キーボードさえあれば・・・。一万超のキーボードを買う客層なら、もう 2-3 千円高くなっても問題ないのでは。少なくとも私は脊髄反射で買いますが、そういう方は他にいないのでしょうか。

2012年3月20日 (火)

画像を貼り付ける(1) - お手軽 StaticBitmap

wxRuby でウィンドウ内に画像を貼り付けたい。そもそも、ruby/Tk で間に合わなくて、あれこれ調べた結果 wxRuby までたどり着いたので、画像処理なんて高度なことはできなくてもよいのですが、任意の画像ファイルを読み込んで、適宜拡大・縮小するくらいのことはしたいわけです。ruby/Tk では素のままでは jpeg を扱えなくて、jpeg を使える拡張を施しても、画像の拡大・縮小で行き詰りました。

この週末で、漸く画像の貼り付けまで手が届きました。手元の参考書 を見ても、とっかかりがつかめなかったので、"Wx::Image" で検索をかけてみましたが、wxRuby の情報は少ないですね。仕方がないので wxPython の情報を参考に try & error です。

まず、とりあえず簡単に表示するものです。"white wheelsのメモ" を参考にさせていただきました。

# encoding: UTF-8

require 'wx'

class MyApp < Wx::App
  def on_init
    @main_frame = FrameMain.new
    @main_frame.show
  end
end

class FrameMain < Wx::Frame
  def initialize( title = "" )
    super( nil, -1, title, :size => [450,500] )
    sizer_top = Wx::BoxSizer.new( Wx::VERTICAL )
    set_sizer( sizer_top )
    sizer_top.add( make_button(), 0, Wx::ALIGN_CENTER_HORIZONTAL )
    sizer_top.add( @panel = MyPanel.new( self ), 1, Wx::EXPAND )
  end

  def make_button
    button = Wx::Button.new(self, -1, "Browse image file" )
    evt_button( button ) { |e| browse_file( e ) }
    button
  end

  def browse_file( evt )
    dlg = Wx::FileDialog.new( self, 'file 選択' )
    if ( dlg.show_modal == Wx::ID_OK )
      @panel.load_image( dlg.get_path )
    end
  end
end

class MyPanel < Wx::Panel
  def load_image( file_name )
    img = Wx::Image.new( file_name )
    img = img.rescale( img.width/2, img.height/2 )
    Wx::StaticBitmap.new( self, -1, img.convert_to_bitmap, [0,0], [img.width, img.height ] )
  end
end

MyApp.new.main_loop

FrameMain#initialize の中で、Wx::Button と MyPanel を new して、BoxSizer( HORIZONTAL ) で縦に並べて貼り付けています。

button をクリックすると、FrameMain#browse_file に飛んで、file 選択 dialog が開きます。取得した file 名を MyPanel#load_image に渡します。本題の画像表示は、MyPanel#load_image 内で処理します。

Wx::Image#new のマニュアルを参照すると、読み込む画像ファイルの形式を指定することも可能ですが、type に default で Wx::BITMAP_TYPE_ANY が指定されているので、file 名を指定するだけで、読み込めます。

ここで重要なのが、Wx::Image は、そのままでは画面表示に使えないことです。あちこち読めば明記してあるのですが、いかんせん、英語のドキュメントばかりだと、気づくのに暫くかかります。Wx::Image のマニュアル の先頭にも、「Platform に依存しない形で画像データを扱えるようにカプセル化されているが、少なくとも現状では、そのままでは Device Context に描画できない」といったことが書かれています。

と、いうことで、Image のままでできる処理は済ませた上で、最後に Wx::Bitmap に変換します。上記のコードであれば、先に img.rescale() で、画像を縦・横ともに 1/2 に縮小処理を済ませておきます。

画面に表示するのに、一番手っ取り早そうなのが、Wx::StaticBitmap#new() です。第三引数に画像データを渡しますが、ここで、img.convert_to_bitmap で、Wx::Image を Wx::Bitmap に変換したものを渡します。これで画面に表示されるのですが、Panel が self の文脈で StaticBitmap.new() するだけで、Panel に画像が表示されるギミックが全くわかりません。StaticBitmap と Panel の instance がどこで結びついているのか? 恐らく内部で DC を呼び出したりしてるのでしょうが。

で、上記のコードは、 ウィンドウサイズを、画像が隠れるほど縮小してから元に戻しても、隠れた部分の画像も無事再描画してくれますし、決して悪くないのですが、一旦表示した画像を適宜拡大・縮小することができません (少なくとも方法がわかりません)。もっと悪いことに、button をもう一度クリックして、別のファイルを選んでも、前の画像が消えません (これも、少なくとも、消し方がわかりません)。

と、いうことで、一度表示してそれっきりならともかく、途中で画像を変えたり、拡大・縮小するには、この方法では不足です。次回はとうとう Device Context に手を出してみます。

Device Context はいまだによくわからないんですが、まぁ、わからなくても使えればよいということで・・・。

« 2012年2月 | トップページ | 2012年5月 »