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年5月 | トップページ | 2012年7月 »

2012年6月の1件の記事

2012年6月27日 (水)

画像を貼り付ける(4) -サムネイルから子画面を開く

前回の記事で、どのサイトを参考にしたのかわからなくなったと書きましたが、やっと探し出せました。"Alone Like a Rhinoceros Horn のこの記事 "を参考に書き始めたわけです。

そこからあれこれと試行錯誤して、ようやくここまできました。今回で、サムネイル画面から、ダブルクリックで子画面を開いて、その画面を拡大・縮小したり、もとのファイルを削除できるようにします。ネットから収拾した「イケナイ」画像の山から、不要な画像や重複した画像を削除して整理できます。まあ、わざわざ作らなくても OS 標準のツールとエクスプローラーで充分なのですが(^^; 


thumb_child.rb


# encoding: UTF-8


require 'wx'

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

class ThumbnailsFrame < Wx::Frame
  THUMB_W = 160; THUMB_H = 120
  THUMB_SIZE = Wx::Size.new( THUMB_W, THUMB_H )

  def initialize
    super(nil, :title => "Thumbnails", :size => [720, 480])
    set_sizer(Wx::BoxSizer.new( Wx::VERTICAL ))
    create_status_bar

    load_button = Wx::Button.new(self, :label => "Load...")
    evt_button( load_button ) { |evt| on_load }
    get_sizer.add(load_button, 0, Wx::ALIGN_CENTER_HORIZONTAL)

    @thumb_list = Wx::ListCtrl.new(self, :style => Wx::LC_ICON)
    get_sizer.add_item(@thumb_list, :proportion => 1, :flag => Wx::EXPAND)

    evt_list_item_activated( @thumb_list ) { |e| thumb_activated( e ) }
    @thumb_list.evt_size { |e| refresh_thumb_list }
  end

  def delete_item( index )
    path = @thumb_list.item( index ).data
    @thumb_list.delete_item( index )
    File.delete( path )
    refresh_thumb_list
  end

  private
  def on_load
    dlg = Wx::DirDialog.new(self)
    if ( dlg.show_modal == Wx::ID_OK )
      set_status_text("サムネイルを作成中...")
      load_thumbnails(dlg.get_path)
      set_status_text("")
    end
  end

  def load_thumbnails(dir)
    image_list = Wx::ImageList.new(THUMB_W, THUMB_H)
    @thumb_list.set_image_list(image_list, Wx::IMAGE_LIST_NORMAL)

    item = Wx::ListItem.new
    glob_pat = %w[jpg png].map{|x| "#{dir}/*.#{x}".gsub(/\\/, '/')}.join("\0")
    Dir.glob( glob_pat ).sort.each do |img_file|
      idx = image_list.add(thumbnail_bitmap(img_file))
      unless ( idx < 0 )
        item.id = idx
        item.image = idx
        item.data = img_file
        @thumb_list.insert_item( item )
      end
    end
  end

  def thumbnail_bitmap(file)
    img = Wx::Image.new(file)
    img_w, img_h = img.get_width, img.get_height
    ratio_w, ratio_h = THUMB_W.to_f/img_w, THUMB_H.to_f/img_h

    if ( ratio_w < ratio_h )
      new_h = (img_h * ratio_w).to_i
      img.rescale(THUMB_W, new_h)
      img.resize(THUMB_SIZE, Wx::Point.new( 0,(THUMB_H - new_h)/2 ))
    else
      new_w = (img_w * ratio_h).to_i
      img.rescale(new_w, THUMB_H)
      img.resize(THUMB_SIZE, Wx::Point.new( (THUMB_W - new_w)/2,0 ))
    end
    Wx::Bitmap.from_image(img)
  end

  def thumb_activated( evt )
    ViewFrame.new( self, evt.index, @thumb_list.item_data( evt.index ) ).show
  end

  def refresh_thumb_list
    @thumb_list.each do |i|
      item = @thumb_list.item(i)
      item.text = "current index = #{item.id}"
      @thumb_list.set_item( item )
    end
    @thumb_list.sort_items{ |a,b| a <=> b }
  end
end

class ViewFrame < Wx::Frame
  def initialize( parent, index, path )
    @index = index
    title = "forked index = #{index} : #{path}"

    super( parent, -1, title, :size => [450,500] )

    sizer_top = Wx::BoxSizer.new( Wx::VERTICAL )
    @sizer_button = Wx::BoxSizer.new( Wx::HORIZONTAL )
    @sizer_button.add_stretch_spacer
    @sizer_button.add( make_resize_button(), 0, Wx::ALIGN_CENTER_HORIZONTAL )
    @sizer_button.add( make_delete_button(), 0, Wx::ALIGN_CENTER_HORIZONTAL )
    @sizer_button.add_stretch_spacer

    set_sizer( sizer_top )
    sizer_top.add( @sizer_button, 0, Wx::EXPAND )
    sizer_top.add( @panel = ImagePanel.new( self ), 1, Wx::EXPAND )
    @panel.load_image( path )
#    @sizer_height = @sizer_button.size.height
  end

  def make_resize_button
    button = Wx::Button.new( self, -1, "Original Size" )
    evt_button( button ) { |e| resize_to_original( e ) }
    button
  end

  def make_delete_button
    button = Wx::Button.new(self, -1, "Delete Me" )
    evt_button( button ) { |e| delete_me( e ) }
    button
  end

  def resize_to_original( evt )
    size = @panel.get_original_size
    size.height += @sizer_button.size.height
    self.set_client_size( size )
  end

  def delete_me( evt )
    dlg = Wx::MessageDialog.new(self, "本当に削除してよろしいですか ?", "Confirm" )
    if dlg.show_modal == Wx::ID_OK
      self.parent.delete_item( @index )
      self.close
    end
  end

end

class ImagePanel < Wx::Panel
  OriginalImage = Struct.new( :image, :width, :height )

  def initialize( *args )
    super
    evt_size { |evt| draw_image }
    evt_paint { |evt| draw_image }
  end

  def load_image( file_name )
    image = Wx::Image.new( file_name )
    @org_img = OriginalImage.new( image, image.width, image.height )
    draw_image
  end

  def get_original_size
    Wx::Size.new( @org_img.width, @org_img.height )
  end

  def draw_image
    return unless( @org_img )

    width = self.get_client_size.width
    height = self.get_client_size.height
    ratio_w, ratio_h = width.to_f/@org_img.width, height.to_f/@org_img.height

    new_scale = if ( ratio_w < ratio_h )
      [ width, (@org_img.height * ratio_w).to_i ]
    else
      [ (@org_img.width * ratio_h).to_i, height ]
    end
    bitmap = @org_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

 前回までと比べて、Thumbnails_Frame の部分にはいくつか手を加えてあります。まず、initialize()

    evt_list_item_activated( @thumb_list ) { |e| thumb_activated( e ) }
    @thumb_list.evt_size { |e| refresh_thumb_list }

evt_list_item_activated() で、特定のサムネイルがダブルクリックされたときの処理を割り付けています。Wx::ListCtrl のドキュメント を参照すると evt_list_item_activated( id ) には、引数として id を渡すのですが、これが何の id か、当初わかりませんでした。てっきり、ダブルクリックする対象としての ListItem の instance を渡す ( wxRuby では、object の instance を渡すと、WxWidget における window id を渡したのと同じ挙動をすることになっています) のかと思ったのですが、いろいろ渡してみたところ、ListCtrl の instance を渡せばよいことがわかりました。

2 行目の @thumb_list.evt_size{} を追加したことで、サムネイル表示 window を拡大・縮小しても、そのたびサムネイルが再配置されます。これは前回まではできていなかったことです。

@tuhmb_list.evt_size{} 内から呼び出される ThumbnailsFrame#refresh_thumb_list() は以下のようになっています。

  def refresh_thumb_list
    @thumb_list.each do |i|
      item = @thumb_list.item(i)
      item.text = "current index = #{item.id.to_s}"
      @thumb_list.set_item( item )
    end
    @thumb_list.sort_items{ |a,b| a <=> b }

  end

前半の @thumb_list.each do ... end の部分は、なければないで動きます。必須なのは @thumb_list.sort_items{ |a,b| ... } の部分。ちなみに、a, b には、ListItem#get_data の部分が渡されます。ThumbnailsFrame#load_thumbnails() の中で、ListItem#data に画像ファイル名を設定していますので、実際には文字列比較が行われています。data に値を設定していないときにどのような挙動になるのかは確認していません。

@thumb_list に要素を追加・削除した場合には、画面表示の前に sort_items{} が必要になるのはわかりますが、window size を変えるだけであれば sort しなくてもすみそうなものなのですが・・・。

なぜか、sort_items{} 以外の method を呼んでも、window size 変更時にサムネイルが再配置されません。どうにも納得できないので、他に良い方法があれば、是非教えてください。

サムネイルをダブルクリックしたときの動作は、ThumbnailsFrame#thumb_activated() に記述しています。なんのことはない、ViewFrame を開いて、子画面に画像を表示するだけです。"画像を貼り付ける(2) - まじめに draw_bitmap" で書いたコードに近いのですが、window size を変更した後にドットバイドットで原寸大表示できる window size に変更するボタンと、元画像ファイルを削除するボタンを実装しました。

原寸大表示の実処理は ViewFrame#resize_to_original() で記述しています。@panel に、元画像の画像のサイズを問い合わせて、それにボタン表示領域の高さを加えたサイズを ViewFrame の表示領域サイズとして設定します。

resize の度ごとに @sizer_button の高さを参照するのも無駄なので、当初は ViewFrame#initialize() の中で、@size_heght として buffering してみたのですが、このタイミングでは @sizer_button の高さは 0 にしかなりません。どうやら、実際に表示されるまでは、sizer のサイズは確定しないようですね。おもしろいですね。

delete ボタンをクリックすると、ViewFrame#delete_me() の中で確認ダイアログを出した上で、親の method である ThumbnailsFrame#delete_item() を呼び出します。 ThumbnailsFrame#delete_item() 内で画像ファイルを削除し、@thumb_list から ListItem を削除します。サムネイルを一枚減らさなければならないので、refresh_thumb_list() を call し、sort_items() だけでなく、ListItem#text に通し番号を付け直します。この text は、サムネイル画面で、サムネイル画像の説明文字列として表示されます。

これで、特定のフォルダ下の全画像をサムネイル表示し、子画面で個別の画像を表示し、不要な画像を削除するプログラムができました・・・と言いたいところなのですが。実は、このコードでは、意図通りの動作をしません。試しにサムネイル一覧から、別の画像で子画面を 2 枚開いて、その 2 枚を削除してみると・・・意図したのと違う画像が削除されてしまいます。

次回は、どこに問題があるのかを解説し、次々回で、対策を施したコードを示す予定です。

ではでは。

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