ホームページ開発ツール>REALbasic Mach-O Plugin 覚書・応用編2・アイコンの取得

 REALbasic Mach-O Plugin 覚書

応用編2・アイコンの取得

目次
 はじめに

 今回はアイコン画像を取得するプラグインの作成に関する話題です。

 アイコン画像には、
  1. システムが用意しているアイコン(フォルダやディスク等)
  2. アプリケーションがそれぞれ用意している固有のアイコン
がありますが、今回はこのそれぞれを取得するエントリを持つプラグインの作成にチャレンジしてみます。


 まずデバッグ用プロジェクトを作る

 プラグインの作成で最も困ることは、デバッグです。
 Xcode上でオンラインでデバッグすることができない(というか、単に知らないだけかも。もしオンラインでデバッグできる方法をご存知でしたらお知らせ頂けると嬉しいです。)ため、プラグインをビルドしてはREALbasicに組み込んで実行し、ダメならまたXcodeに戻って書き直す、という、効率の悪いことをしなければなりません。
(おまけにC言語やCarbon APIは不慣れなため、単純なミスもしばしば。)

 それが今回は、アイコンを取得して表示するCarbonアプリケーションのサンプルが公開されていましたので、これを使わせて頂きました。

 参考サイト(1):ライト、ついてますか: Development アーカイブ [qa1414]

 これで、デバッグ効率が大幅にアップしました。
 なお、上記サンプルで、ウィンドウ位置がずれる場合は、
DemoMainWindowEventHandler(EventHandlerCallRef myHandler, EventRef event, void *userData);

…
InstallWindowEventHandler(window, NewEventHandlerUPP(DemoMainWindowEventHandler), GetEventTypeCount(mainSpec), mainSpec, (void *)window, NULL);
RepositionWindow(window, NULL, kWindowCascadeOnMainScreen);  <-- 追加
ShowWindow(window);
 アイコンの色が化ける場合は、
pascal OSStatus DemoMainWindowEventHandler(EventHandlerCallRef myHandler, EventRef event, void *userData)

…
RGBColor color;
color.red = 0;    <-- 追加
color.green = 0;  <-- 追加
color.blue = 0;   <-- 追加

 アイコン画像を取得するプラグインを作る(アルゴリズム編)

 上記サンプルで使用している「IconRefを取得してContextに描画する」という方法は、今回は使えそうにありません。
 というのもこれは、どうもアイコン群の先頭にある?アイコンのみ持ってくるようで、任意のアイコンを取得できなさそうだからです。
(個々のアイコンを指定するパラメータがない。)

 また、アルファチャンネル込なので、Quartzで表示する分にはいいのですが、REALbasicで扱う場合は、アイコンとマスクを別々に取得して、REALbasic側で合成してやる必要があります。

 個々のアイコンを取得するには、IconSuiteかIconFamiryを使うことになります。
 ただ、IconSuite関連のAPIを見ると「Deprecated in Mac OS X v10.5」が多いので、今後のことを考えるとIconFamiryを使った方がよさそうです。
 そこで、IconRefをIconFamiryに変換して、その後、任意のアイコンを取得する、という方式でいくことにしました。

 調べた結果、
 GetIconFamilyData()
 SetIconFamilyData()
すればよさそうだったので、以下のようにしてみたのですが、うまくいきません。(以下は抜粋)
status = GetIconRef( kOnSystemDisk, kSystemIconsCreator, kGenericFolderIcon, &iconRef );  // フォルダアイコンをIconRef形式で取得
status = IconRefToIconFamily( iconRef,kSelectorAllAvailableData,&iconFamily );  // IconRefをIconFamilyに変換
status = GetIconFamilyData( iconFamily, kThumbnail32BitData, hndl );  // IconFamilyからサムネイルアイコンをraw形式(ハンドル)で取得
status = SetIconFamilyData( iconFamily2, kThumbnail32BitData, hndl );  // rawデータをIconFamilyに変換
status = RegisterIconRefFromIconFamily( kSystemIconsCreator, kThumbnail32BitData, iconFamily2, &iconRef2 );  // IconFamilyをIconRefに変換
status = PlotIconRefInContext( context,&iconRect, kAlignNone, kTransformNone, &color, kPlotIconRefNormalFlags, iconRef2 );  // IconRefを描画
 さらに調べてあれこれ試した結果、GetIconFamilyData()で取得したHandleから直接GWorldを作成してやれば取得できることが分かりました。(以下は抜粋)
status = GetIconRef( kOnSystemDisk, kSystemIconsCreator, kGenericFolderIcon, &iconRef );  // フォルダアイコンをIconRef形式で取得
status = IconRefToIconFamily( iconRef, kSelectorAllAvailableData, &iconFamily );  // IconRefをIconFamilyに変換
status = GetIconFamilyData( iconFamily, kThumbnail32BitData, hndl );  // IconFamilyからサムネイルアイコンをraw形式(ハンドル)で取得
status = NewGWorldFromPtr( &GWorld, k32ARGBPixelFormat, &srcRect, NULL, NULL, 0, *hndl, 128*4 );  // rawデータからGWorld作成
…
CopyBits( (BitMap *)*PixMap, (BitMap *)*dpix, &srcRect, &dstRect, srcCopy, NULL );  // 描画(この場合はウィンドウに)
 なお、ここで気をつける点としては、
  1. マスクは、カラーパレットを指定する必要がある。(指定しないと色が化ける。)(注:8bitカラーも指定した方がいいかもしれない。)
    status = NewGWorldFromPtr( &GWorld, k8IndexedGrayPixelFormat, &srcRect, GetCTable( k8IndexedGrayPixelFormat ), NULL, 0, *hndl, 128*1 );
  2. 1bitマスクはアイコンデータの後ろに含まれているため、オフセットを与えて取り出す。
    status = NewGWorldFromPtr( &GWorld, k1MonochromePixelFormat, &srcRect, GetCTable( k1MonochromePixelFormat ), NULL, 0, *hndl+128, 32/8 );
 次に、ファイルからアイコンを取得する方法ですが、候補として、
・ReadIconFromFSRef()
・GetIconRefFromFileInfo()
を見つけました。

 ReadIconFromFSRefは、アイコンファイル自身(アプリケーションパッケージのResources内に含まれる.icnsファイル)を指定してアイコンを取得するものですが、GetIconRefFromFileInfoは、ファイルを指定すれば(Finderが表示している)アイコンを探し出して持ってきてくれるものです。

 取得方式がだいぶ異なるため、目的に応じて使い分けるべきでしょうが、ここではより柔軟性の高い(アイコンの実体がどこにあるかを考えなくていい)GetIconRefFromFileInfoを使うことにしました。


 アイコン画像を取得するプラグインを作る(実装編)

 ウィンドウへの描画が確認できたところで、プラグイン固有の処理に書き換えます。
(例えば、CopyBitsはウィンドウに描画するためのものなので、プラグインではPictureに書き込む処理に変更します。)

 また、アイコンにはシステム標準とアプリ固有があるので、両方に対応できるよう、エントリを2つ用意しました。
 複数のエントリは以下のように、関数本体部分とREALmethodDefinitionはエントリ数分、PluginEntryはその中身をエントリ数分記述します。
(システム標準はGetIconFromSystem、アプリ固有はGetIconFromFileという名前としました。なお、iconIDはアイコン/マスクの種別用、iconNoはシステムアイコンの種類指定用です。)
#include "rb_plugin.h"
#include "REALplugin.h"

static REALpicture GetIconFromFile( REALfolderItem f, int iconID )
{
…
}

static REALpicture GetIconFromSystem( int iconNo, int iconID )
{
…
}

REALmethodDefinition GetIconFromFileDefn = {
    (REALproc) GetIconFromFile,
    REALnoImplementation,
    "GetIconFromFile(f As FolderItem, iconID As Integer) As Picture"
};
REALmethodDefinition GetIconFromSystemDefn = {
    (REALproc) GetIconFromSystem,
    REALnoImplementation,
    "GetIconFromSystem(iconNo As Integer, iconID As Integer) As Picture"
};

void PluginEntry(void)
{
    REALRegisterMethod(&GetIconFromFileDefn);
    REALRegisterMethod(&GetIconFromSystemDefn);
}
 ソースコードは、結局、以下の通りとなりました。
 注1)コードは作者が学習用に試作したものであり、その内容は無保証です。使用した結果、何らかの障害が発生しても、作者は一切の責任を負いません。
 注2)2つの関数は共通部分の方がずっと多いので、共通部分を括り出すか、一本化してパラメータでスイッチするかした方がいいでしょう。

 なお、プラグイン作成手順は前回と同じですので、そちらを参照ください。


 プラグインをテストする

 動くかどうか、テストします。
  1. REALbsicを起動する。
  2. Window1のプロパティに以下を追加。
    p1 As Picture
    p2 As Picture
    p3 As Picture
    
  3. Window1にCanvasを置き、Paintイベントに以下を記述。
    if p3<>nil then
      g.DrawPicture p3,0,0
    end if
    
  4. Window1にPopupMenuを2個置き、InitialValueに以下を記述。(上段はPopupMenu1用、下段はPopupMenu2用)
    Thumbnail32Bit
    Huge32Bit
    Huge8Bit
    Large32Bit
    Large8Bit
    Small32Bit
    Small8Bit
    Mini8Bit
    
    -----
    kGenericFolderIcon
    kDropFolderIcon
    kMountedFolderIcon
    kOpenFolderIcon
    …
    (ソースコードのSystemIcon[]の項目をコピー)
    
  5. Window1にPushButtonを2個置き、PushButtonのActionイベントに以下を記述。(上段はPushButton1用、下段はPushButton2用)
    if p3<>nil then
      p3=nil
    end if
    p3=NewPicture(128,128,32)
    
    select case PopupMenu1.ListIndex
    case 0  // Thumbnail32Bit
      p1=GetIconFromSystem(PopupMenu2.ListIndex,0)  // Icon
      p2=GetIconFromSystem(PopupMenu2.ListIndex,8)  // Mask
    case 1  // Huge32Bit
      p1=GetIconFromSystem(PopupMenu2.ListIndex,1)
      p2=GetIconFromSystem(PopupMenu2.ListIndex,9)
    case 2  // Huge8Bit
      p1=GetIconFromSystem(PopupMenu2.ListIndex,2)
      p2=GetIconFromSystem(PopupMenu2.ListIndex,10)
    case 3  // Large32Bit
      p1=GetIconFromSystem(PopupMenu2.ListIndex,3)
      p2=GetIconFromSystem(PopupMenu2.ListIndex,11)
    case 4  // Large8Bit
      p1=GetIconFromSystem(PopupMenu2.ListIndex,4)
      p2=GetIconFromSystem(PopupMenu2.ListIndex,12)
    case 5  // Small32Bit
      p1=GetIconFromSystem(PopupMenu2.ListIndex,5)
      p2=GetIconFromSystem(PopupMenu2.ListIndex,13)
    case 6  // Small8Bit
      p1=GetIconFromSystem(PopupMenu2.ListIndex,6)
      p2=GetIconFromSystem(PopupMenu2.ListIndex,14)
    case 7  // Mini8Bit
      p1=GetIconFromSystem(PopupMenu2.ListIndex,7)
      p2=GetIconFromSystem(PopupMenu2.ListIndex,15)
    end select
    
    if p1<>nil then
      p3.Graphics.DrawPicture p1,0,0
    end if
    if p2<>nil then
      p3.Mask.Graphics.DrawPicture p2,0,0
    end if
      
    if p1<>nil and p2<>nil then
      Canvas1.Refresh    
    else
      msgBox "err"
    end if
    
    -----
    Dim dlg as OpenDialog
    Dim f as FolderItem
    dlg= New OpenDialog
      
    f=dlg.ShowModal()
    if f=nil then
      return
    end if
    
    if p3<>nil then
      p3=nil
    end if
    p3=NewPicture(128,128,32)
    
    select case PopupMenu1.ListIndex
    case 0  // Thumbnail32Bit
      p1=GetIconFromFile(f,0)  // Icon
      p2=GetIconFromFile(f,8)  // Mask
    case 1  // Huge32Bit
      p1=GetIconFromFile(f,1)
      p2=GetIconFromFile(f,9)
    case 2  // Huge8Bit
      p1=GetIconFromFile(f,2)
      p2=GetIconFromFile(f,10)
    case 3  // Large32Bit
      p1=GetIconFromFile(f,3)
      p2=GetIconFromFile(f,11)
    case 4  // Large8Bit
      p1=GetIconFromFile(f,4)
      p2=GetIconFromFile(f,12)
    case 5  // Small32Bit
      p1=GetIconFromFile(f,5)
      p2=GetIconFromFile(f,13)
    case 6  // Small8Bit
      p1=GetIconFromFile(f,6)
      p2=GetIconFromFile(f,14)
    case 7  // Mini8Bit
      p1=GetIconFromFile(f,7)
      p2=GetIconFromFile(f,15)
    end select
    
    if p1<>nil then
      p3.Graphics.DrawPicture p1,0,0
    end if
    if p2<>nil then
      p3.Mask.Graphics.DrawPicture p2,0,0
    end if
    
    if p1<>nil and p2<>nil then
      Canvas1.Refresh
    else
      msgBox "err"
    end if
    
  6. 実行する。
  7. PopupMenu1でID、PopupMenu2で種別を指定後、PushuButton1を押す。アイコン画像が表示されればOK。
    (ただし、アイコンによっては対応するIDの画像が含まれていない場合がある。)
  8. PopupMenu1でIDを指定後、PushuButton2を押し、ダイアログでファイル(アプリ、書類いずれも可)を指定する。アイコン画像が表示されればOK。
    (ただし、アイコンによっては対応するIDの画像が含まれていない場合がある。)


 おわりに

 とりあえず取得はできましたが、あまりに泥臭すぎるので、他にもっとスマートな方法があるような気がしています。
 時間ができたら、もう少し調べてみたいです。

 注)今回のサンプルはあくまで動作確認用で、配布を前提としたものではありません。(配布用とするにはいくつかの配慮が必要になります。)


 お世話になったサイト

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

 参考サイト(1):ライト、ついてますか: Development アーカイブ [qa1414]


 更新履歴

 2008.03.13 新規作成


[Home]  [MacSoft]  [Donation]  [History]