ホームページ開発ツール>Xojo / Real Studio Trial and Error・CocoaのDeclareでアイコンを取得してみる

 Xojo / Real Studio Trial and Error

CocoaのDeclareでアイコンを取得してみる

目次
 はじめに

 以下は、Xojo Cocoaビルドについての話題です。

 アイコンの取得は以前、プラグインで行う方法を模索してみましたが、Declareの方がよりお手軽なので、試してみました。

 なお検証には、Xojo 2016 Release 3を用いています。(Mac mini mid 2010 + macOS 10.13.4 High Sierra)
注)Retinaディスプレイ上での挙動については、環境がないのでここでは触れていません。また、XojoにもHiDPI Support機能がありますが、これも同じ理由でノータッチです。

 方針

 アイコン画像の取得には、アプリケーションを指定して抜き出す方法や、icnsファイルを指定する方法等がありますが、今回は、icnsファイルからにしてみます。
 任意のicnsファイルを指定して取得するようにしてもいいのですが、ここではOS標準のアイコンを、システムファイルから取得することにします。
 OS標準のアイコンの在り処は、以下の記事で示されています。

 参考サイト(1):OS Xのシステム標準の画像やアイコンはどこに入っている?(その2)|Mac - 週刊アスキー

 取得の方法としては、以前プラグインでも行なった、NSWorkspaceクラスのiconForFile:メソッドとTIFFRepresentationを利用します。
 TIFFデータ取得後は、これもこちらでやったように、一度PNGに変換し、そこからXpjoのPictureに変換する方式をとります。

 icnsファイルに含めるアイコン画像の種別は、以前にも参照させて頂いたサイトにある通り、10種類です。
 ですが、上述のOS標準アイコンをプレビューで表示してみると、多くは8種類(16x16(72dpi)と32x32(72dpi)が抜け)ですが、他に1〜4種類程度のものも見られます。
 このこともあって、ここではサイズを指定するのではなく、含まれるアイコンをリストアップする方式にします。

 なお、XojoのPictureは、DPI値を反映するため、144DPIの画像は画面上では半分の大きさで表示されます。

 以上を踏まえ、仕様は以下の通りとしました。

 Xojoでの実装
【ソースコードのコピー&ペーストについて】
ソースコード(グレー背景部分の全文)をコピーし、指定のウィンドウ/クラスにペーストすると、(新規作成して名前等を個別にコピー&ペーストしなくても)復元されます。
ただし、この方法は、メソッドでは問題ないようですが、イベント/アクション/プロパティでは不安定?なので、ペーストできない場合は、各項目のカッコ内を適用して下さい。
  1. Xojoで新規プロジェクトを作成
  2. ファイルタイプグループをプロジェクトに追加(Name:FileTypes1)し、一般的なファイルタイプの追加の中から、「image/png」を追加。
  3. Window1に。BevelButton(Name:BevelButton1)、Canvas(Name:Canvas1)、Listbox2個(Name:Listbox1, ColumnCount:2、Name:Listbox2, ColumnCount:3)、PushButton(Name:PushButton1)、TextField(Name:TextField1)を追加
  4. 以下をBevelButton1にペースト(できなければ、Sub - Endの間をActionイベントに記述)
    Sub Action() Handles Action
      Dim dlg As New SaveAsDialog
      Dim f As FolderItem
      
      // ダイアログ表示
      dlg.Filter=FileTypes1.Png
      f=dlg.ShowModalWithin(Self)
      if f=nil then
        return
      end if
      
      // ファイル出力
      pic.Save(f,Picture.SaveAsPNG)
    End Sub
    
  5. 以下をCanvas1にペースト(できなければ、Sub - Endの間をPaintイベントに記述)
    Sub Paint(g As Graphics, areas() As REALbasic.Rect) Handles Paint
      if pic<>nil then
        g.DrawPicture pic,0,0
      end if
    End Sub
    
  6. 以下をListbox1にペースト(できなければ、Sub - Endの間をChangeイベントに記述)
    Sub Change() Handles Change
      fItem=GetFolderItem(me.Cell(me.ListIndex,1),3)
      GetIconInfo()
    End Sub
    
  7. 以下をListbox2にペースト(できなければ、Sub - Endの間をChangeイベントに記述)
    Sub Change() Handles Change
      if me.ListIndex>=0 then
        // Pictureの取得
        pic=GetIconPicture(me.ListIndex)
        // Pictureの諸元を表示
        Label1.Text="width x height = "+str(pic.Width)+" x "+str(pic.Height)+" : resolution = "+str(pic.VerticalResolution)+" x "+str(pic.HorizontalResolution)
        // Pictureの再描画
        Canvas1.Refresh
      end if
    End Sub
    
  8. 以下をPushButton1にペースト(できなければ、Sub - Endの間をActionイベントに記述)
    Sub Action() Handles Action
      GetList()
    End Sub
    
  9. 以下をWindow1にペースト(できなければ、Sub - Endの間をOpenイベントに記述)
    Sub Open() Handles Open
      // OS標準アイコンを納めたフォルダへのパス
      TextField1.Text="/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/"
      
      // ファイルリストの生成
      GetList()
    End Sub
    
  10. 以下をWindow1にペースト
    Protected Sub ExtractBodyAndExtn(st As String, byRef body As String, byRef ext As String)
      Dim i, cnt As Integer
      
      // ピリオドの数をカウント
      cnt=CountFields(st,".")
      
      // ピリオドがなければ入力をbody、拡張子を空として返す
      if cnt<=1 then
        body=st
        ext=""
        return
      end if
      
      // 最後のピリオド以降を拡張子とみなす
      ext=NthField(st,".",cnt)
      
      // 最後のピリオド以前をbodyとみなす
      body=""
      for i=1 to CountFields(st,".")-2
        body=body+NthField(st,".",i)+"."
      next
      body=body+NthField(st,".",cnt-1)
    End Sub
    
  11. 以下をWindow1にペースト
    Protected Sub GetIconInfo()
      // ファイルが取得できなければ戻る
      if fItem=nil or fItem.Exists=false then
        return
      end if
      
      // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。
      Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
      
      // 指定された.icnsファイルへのパスからイメージを取得
      Dim image As Ptr = NSClassFromString("NSImage")
      Declare Function alloc Lib "Cocoa" Selector "alloc" (receiver As Ptr) As Ptr
      image = alloc(image)
      Declare Function initWithContentsOfFile Lib "Cocoa" Selector "initWithContentsOfFile:" (receiver As Ptr, path As CFStringRef) As Ptr
      image = initWithContentsOfFile(image, fItem.NativePath)
      
      // TIFF形式でアイコン群を取得
      Declare Function TIFFRepresentation Lib "Cocoa" Selector "TIFFRepresentation" (receiver As Ptr) As Ptr
      Dim pnt1 As Ptr = TIFFRepresentation(image)
      Dim bitmapImages As Ptr = NSClassFromString("NSBitmapImageRep")
      Declare Function imageRepsWithData Lib "Cocoa" Selector "imageRepsWithData:" (receiver As Ptr, path As Ptr) As Ptr
      bitmapImages = imageRepsWithData(bitmapImages, pnt1)
      
      // 群の要素数を取得
      Declare Function myCount Lib "Cocoa" Selector "count" (receiver As Ptr) As Integer
      Dim cnt As Integer = myCount(bitmapImages)
      
      // 要素のループ
      Declare Function objectAtIndex Lib "Cocoa" Selector "objectAtIndex:" (receiver As Ptr, path As Integer) As Ptr
      Declare Function mySize Lib "Cocoa" Selector "size" (receiver As Ptr) As NSSize
      Declare Function pixelsWide Lib "Cocoa" Selector "pixelsWide" (receiver As Ptr) As Integer
      Declare Function pixelsHigh Lib "Cocoa" Selector "pixelsHigh" (receiver As Ptr) As Integer
      Declare Function bytesPerRow Lib "Cocoa" Selector "bytesPerRow" (receiver As Ptr) As Integer
      Dim bitmapImageRep As Ptr
      Dim k, bpr, ww, hh As Integer
      Dim iconSize As NSSize
      Listbox2.DeleteAllRows  // まず全行削除
      for k=0 to cnt-1
        
        // 要素を取得
        bitmapImageRep = objectAtIndex(bitmapImages, k)
        
        // 情報を取得
        iconSize = mySize(bitmapImageRep)
        ww = pixelsWide(bitmapImageRep)
        hh = pixelsHigh(bitmapImageRep)
        bpr = bytesPerRow(bitmapImageRep)
        
        // 情報を表示
        Listbox2.AddRow str(iconSize.Width)+" x "+str(iconSize.Height)
        ListBox2.Cell(ListBox2.ListCount-1,1)=str(ww)+" x "+str(hh)
        ListBox2.Cell(ListBox2.ListCount-1,2)=str(bpr)
        
      next
    End Sub
    
  12. 以下をWindow1にペースト
    Protected Function GetIconPicture(idx As Integer) as Picture
      // ファイルが取得できなければnilを返す
      if fItem=nil or fItem.Exists=false then
        return nil
      end if
      
      // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。
      Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
      
      // 指定された.icnsファイルへのパスからイメージを取得
      Dim image As Ptr = NSClassFromString("NSImage")
      Declare Function alloc Lib "Cocoa" Selector "alloc" (receiver As Ptr) As Ptr
      image = alloc(image)
      Declare Function initWithContentsOfFile Lib "Cocoa" Selector "initWithContentsOfFile:" (receiver As Ptr, path As CFStringRef) As Ptr
      image = initWithContentsOfFile(image, fItem.NativePath)
      
      // TIFF形式でアイコン群を取得
      Declare Function TIFFRepresentation Lib "Cocoa" Selector "TIFFRepresentation" (receiver As Ptr) As Ptr
      Dim pnt1 As Ptr = TIFFRepresentation(image)
      Dim bitmapImages As Ptr = NSClassFromString("NSBitmapImageRep")
      Declare Function imageRepsWithData Lib "Cocoa" Selector "imageRepsWithData:" (receiver As Ptr, path As Ptr) As Ptr
      bitmapImages = imageRepsWithData(bitmapImages, pnt1)
      
      // 指定されたサイズのアイコンを取得
      Dim bitmapImageRep As Ptr
      Declare Function objectAtIndex Lib "Cocoa" Selector "objectAtIndex:" (receiver As Ptr, path As Integer) As Ptr
      bitmapImageRep = objectAtIndex(bitmapImages, idx)
      
      // falseをNSNumber形式に変換
      Dim numb As Ptr = NSClassFromString("NSNumber")
      Declare Function numberWithBool Lib "Cocoa" Selector "numberWithBool:" (receiver As Ptr, path As Boolean) As Ptr
      numb = numberWithBool(numb, false)
      
      // PNG用オプションのセット
      Dim dict As Ptr = NSClassFromString("NSDictionary")
      Declare Function dictionaryWithObject Lib "Cocoa" Selector "dictionaryWithObject:forKey:" (receiver As Ptr, objt As Ptr, key As CFStringRef) As Ptr
      dict = dictionaryWithObject(dict, numb, "NSImageInterlaced")
      
      // PNG出力(NSPNGFileType = 4)
      Declare Function representationUsingType Lib "Cocoa" Selector "representationUsingType:properties:" (receiver As Ptr, type As Integer, prop As Ptr) As Ptr
      Dim pngData As Ptr = representationUsingType(bitmapImageRep, 4, dict)
      
      // clean up
      Declare Sub release Lib "Cocoa" Selector "release" (receiver As Ptr)
      release(image)
      
      // PNGデータの長さを取得
      Declare Function length Lib "Cocoa" Selector "length" (receiver As Ptr) As Integer
      Dim lng As Integer = length(pngData)
      
      // バイト列の抽出
      Declare Function bytes Lib "Cocoa" Selector "bytes" (receiver As Ptr) As Ptr
      Dim bstream As Ptr = bytes(pngData)
      
      // バイト列をMemoryBlockに1バイトずつコピー
      Dim i As Integer
      Dim mb As new MemoryBlock(lng)
      for i=0 to lng-1
        mb.Byte(i) = bstream.Byte(i)
      next
      
      // MemoryBlockを使ってPictureを生成
      Dim pic As Picture = Picture.FromData(mb)
      
      return pic
    End Function
    
    注)1バイトずつコピーの代替法については、こちらを参照。(2019.03.26)
  13. 以下をWindow1にペースト
    Protected Sub GetList()
      Dim path As String = TextField1.Text
      Dim f As FolderItem = GetFolderItem(path,3)
      
      // 指定されたフォルダ内の全ファイルをチェック
      Dim body, extn As String
      Dim i As Integer
      for i=1 to f.Count
        
        // ファイル名を本体名と拡張子に分解
        ExtractBodyAndExtn(f.Item(i).Name,body,extn)
        if extn="icns" then  // 拡張子がicnsなら
          
          ListBox1.AddRow body  // 本体名を登録
          ListBox1.Cell(ListBox1.ListCount-1,1)=f.Item(i).NativePath  // ファイルパスを登録
          
        end if
        
      next
    End Sub
    
  14. 以下をWindow1にペースト(できなければプロパティに、名前:fItem、データ型:FolderItem、を追加)
    Protected Property fItem as FolderItem
    
  15. 以下をWindow1にペースト(できなければプロパティに、名前:pic、データ型:Picture、を追加)
    Protected Property pic as Picture
    
  16. 他に、NSMakeSize(メソッド)、NSSize(構造体)が必要ですが、それらはmacoslibからコピーさせて頂きました。(上記Window1にコピーする。)
 実行してみたところ、アイコン画像の取得と表示が機能することを確認しました。
S Shot1


 おわりに

 サイズを指定して取得する場合は、(プラグインの時の繰り返しになりますが)所望のサイズがあればそれを、なければ直近または最大サイズを縮小(大きなものがなければ拡大)、になりそうです。
 また、場合によってはDPI値を変更する方法も有効かと思われます。


 お世話になったサイト

 貴重な情報をご提供頂いている皆様に、お礼申し上げます。(以下、順不同)

 参考サイト(1):OS Xのシステム標準の画像やアイコンはどこに入っている?(その2)|Mac - 週刊アスキー
 参考サイト(2):Hys.LOG : MacのRetina対応アイコン


 更新履歴

 2019.03.26 1バイトずつコピーの代替法へのリンクを追加
 2018.05.31 新規作成


[Home]  [MacSoft]  [Donation]  [History]  [Privacy Policy]  [Affiliate Policy]