ホームページ開発ツール>Xojo / Real Studio Trial and Error・CocoaのDeclareでアプリアイコンを作る・前景画像に対応する

 Xojo / Real Studio Trial and Error

CocoaのDeclareでアプリアイコンを作る・前景画像に対応する

目次
 はじめに

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

 前景となる画像を含むアプリケーションアイコンの作成方法について、調べてみました。

 なお検証には、Xojo 2022 Release 4.1を用いています。(Mac mini 2018 + macOS 15.3.1 Sequoia)


 方針

 前回積み残しとなっていた、テキストエディットのペンやXcodeのハンマーのような、前景となる画像を含むケースに対応してみます。
 アップルの公式ドキュメントには、この画像(アップルのガイド図に則り、以下、ツール)についても記述があります。

 参考サイト(1):アプリアイコン | Apple Developer Documentation(macOSの項。ガイド図はこちら

 これを簡易アプリで対応しようとすると、ツールはあらかじめ画像ソフトで作っておく、というのが妥当そうです。
(内部でジェネレートしようとすると、シンプルなものにならざるを得ず、そうなると公式ドキュメントの趣旨からずれてしまう。)

 その上で、いろいろ辻褄合わせをすることになります。
 1. アイコン本体  :背景となるアイコン画像(以下、ベース)レイヤーの上に、ツールを別レイヤーとして重ねる。
 2. ドロップシャドウ:ベースとツールそれぞれ別個に付ける。(ツール内のシャドウは、必要ならあらかじめつけておく。)
 3. グラデーション :ベースのマスク画像にツールのマスク画像を付加して、一遍に処理することで、ムラや漏れが出ないようにする。

 また、これを機会にソースコードを見直し、バラバラだった単色/2分割/外部画像をできるだけ共通化します。
 レイヤーは、先に追加したものが下層になるので、以下の順番とします。
 シャドウ → ベース(内部で単色/2分割/外部画像に分岐)→ 縁 → テキスト → ツール画像 → グラデーション

 前回レポートした、グラデーションが綺麗にかからない場合がある(濃度によっては境界に白浮き/黒沈みが発生する)問題は、軽減するよう努めます。
 いくつか試行しましたが、結局、1024x1024pixel/72dpi/白一色/シャドウとグラデーションなし、の画像(少し細工すれば、前回プロジェクトから作れる)を用意して、これをマスク画像とする方法が、今のところ最もベターです。(無くす方法は見つかっていない。)
そもそもなぜこのようなことになるのか、については、理解が及んでいない。
周縁部とグラデーションにはそれぞれアルファブレンディングが適用されていて、それらがマスクも含めてレイヤーとして独立していることが関係しているのか。
一度ファイルに出力(この時点でレイヤーは展開される)したものを再度取り込んで使うと、ブレンドの仕方が変わるとかして、結果が異なるのかもしれない。
 縁の描画方法も変更して、今回はborderWidthプロパティを設定して塗ることにします。この方が、(1) 縁の作成をベースから独立させられるので融通性がアップする、(2) 内側のカーブを自分で設定しなくて済む、ためです。
 この時、(グラデと同じ事情で?)周縁部にベースカラーが現れるので、縁ありの時はベースサイズを1pixel小さくします。

 あと、細かい話ですが、テキストのx座標は、frameの幅を824pixel(ベースの有効幅)、揃えをcenterにしているため、直感的ではありません。
 frameの幅をテキスト幅、揃えをleftにすれば改善されますが、ここでは(マイナス値が使えるので配置に制約はないため)そのままとしています。
フォントによっては幅や高さが不足する場合があるので、使いたいフォントによっては対策が必要。(幅はイタリックやボールド、高さは、文字の描画Pixel数がフォントサイズ(Point数)を超えるものが該当。)
参考サイト(2):objective c - How to get the width of an NSString? - Stack Overflow
参考サイト(3):How to calculate the height of an NSAttributedString with given width in iOS 6 - Stack Overflow
 座標値の話が出たので、ここでグラデーションのy座標についても述べておきます。(注:x座標はここでは考えなくてよい。)
 Cocoa(macOS)の座標系はLLOなので、y座標の原点はViewの下端(ウィンドウ下端から10pixel上)にあります。グラデーションのStartPointは、ここからの距離になります。(そのため、厳密にアイコン下辺から始めたい場合は、余白の100pixel分ずらす必要がある。)
 一方、カラーは、StartPointが黒、EndPointは白、としていますので、グレースケールとなります。
 色の変化はStartPointから始まり、EndPointで終了します。なので、StartPointの方がEndPointより数値が大きいと、上部が暗くなります。

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

 Xojoでの実装
注:以下の実装では、一部Xojo 2022r4.1ではDeprecatedな機能が使われています。必要なら推奨される機能に置き換えて下さい。
【ソースコードのコピー&ペーストについて】
・ソースコード(グレー背景部分の全文)をコピーし、指定のオブジェクトにペーストすると、(新規作成して名前等を個別にコピー&ペーストしなくても)復元されます。
・ペーストはオブジェクトに行って下さい。オブジェクト内のEvent Handlers/Methods/Properties等にペーストしても、うまくいかない場合があります。
・それでもペーストできない場合は、各項目のカッコ内を適用して下さい。
  1. 前回プロジェクト(フォントパネル対応版)をベースとする
  2. Window1に、DesktopBevelButton1個(Name:BevelButton2, Caption:Get...)、DesktopTextArea1個(Name:TextAreaPP)を置く。(配置はスクリーンショット参照)
  3. 以下をBevelButton2にペースト(できなければ、Sub - Endの間をPressedイベントに記述)
    Sub Pressed() Handles Pressed
      Var f As FolderItem = FolderItem.ShowOpenFileDialog("image/png;image/jpeg;image/tiff;image/gif") // defined as a FileTypeGroup1
      If f = Nil Then
        return
      end if
      
      TextAreaPP.Text=f.NativePath
    End Sub
    
  4. 以下のWindow1のイベントを置き換え(できなければ、Sub - Endの間をOpeningイベントに記述)
    Sub Opening() Handles Opening
      me.Top=me.Top+3
      me.AcceptFileDrop("image/png;image/jpeg;image/tiff;image/gif")
      
      
      // NSFontPanel。引数は、フォントパネルクリックアクションの受け口
      Dim f As NSFontPanel = new NSFontPanel(AddressOf FontSelected)
      
      // NSView。引数は順に、生成したviewのインスタンス、配置するウィンドウ、位置/サイズ
      Dim d As NSViewCanvas = new NSViewCanvas(pViewInst, self, NSMakeRect(10, 10, 1024, 1024))
      
      // アイコンビュー生成(初期化)
      MakeIconView(kReset)
    End Sub
    
  5. Window1のメソッドのうち、FontSelected, GenMsgDlg, RemoveIconView, ShowFontPanelは残す。MakeIcns>ExportIcns、MakeIcnsShell>ExportIcnsShell、SaveImage>ExportImageにリネーム(呼び出し箇所も合わせる)。残りは削除。
  6. 以下をWindow1にペースト
    Protected Sub LoadParam()
      Var f As FolderItem = FolderItem.ShowOpenFileDialog("text/plain") // defined as a FileType
      If f = Nil Then
        return
      end if
      
      Var t As TextInputStream = TextInputStream.Open(f)
      t.Encoding = Encodings.UTF8
      
      Dim vv As String = t.ReadLine
      if vv="0" then
        myRadioButton1.Value = true
      else
        myRadioButton2.Value = true
      end if
      vv = t.ReadLine
      if vv="0" then
        myRadioButton3.Value=true
      elseif vv="1" then
        myRadioButton4.Value=true
      else
        myRadioButton5.Value=true
      end if
      TextFieldBY.Text = t.ReadLine
      ClrCanvas1.pClr = gClrItoC(Val(t.ReadLine))
      TextFieldBX.Text = t.ReadLine
      ClrCanvas2.pClr = gClrItoC(Val(t.ReadLine))
      
      Dim ss As String = t.ReadLine
      if ss="false" then
        CheckBox1.Value = false
      else
        CheckBox1.Value = true
      end if
      TextFieldBW.Text = t.ReadLine
      ClrCanvas3.pClr = gClrItoC(Val(t.ReadLine))
      
      TextFieldSO.Text = t.ReadLine
      TextFieldSR.Text = t.ReadLine
      TextFieldSP.Text = t.ReadLine
      
      TextFieldGS.Text = t.ReadLine
      TextFieldGE.Text = t.ReadLine
      TextFieldGO.Text = t.ReadLine
      
      TextFieldTX.Text = t.ReadLine
      TextFieldTY.Text = t.ReadLine
      COmboBox1.Text = t.ReadLine
      TextFieldTF.Text = t.ReadLine
      TextFieldTS.Text = t.ReadLine
      ClrCanvas4.pClr = gClrItoC(Val(t.ReadLine))
      
      pPath = t.ReadLine  // Dropped File Path
      
      TextAreaPP.Text = t.ReadLine  // Picture File Path
      
      t.Close
      
      ClrCanvas1.Refresh
      ClrCanvas2.Refresh
      ClrCanvas3.Refresh
      ClrCanvas4.Refresh
      
      pFIparam=f
    End Sub
    
  7. 以下をWindow1にペースト
    Protected Function MakeAttrShadow(objectlayer As Ptr, shOffset As CGFloat, shRadius As CGFloat, shOpacity As Single) As Ptr
      // シャドウ属性を付加
      Declare Sub setMasksToBounds Lib "Cocoa" Selector "setMasksToBounds:" (receiver As Ptr, val As Boolean)
      setMasksToBounds(objectlayer, false)
      Declare Sub setShadowOffset Lib "Cocoa" Selector "setShadowOffset:" (receiver As Ptr, rect As CGSize)
      setShadowOffset(objectlayer, CGSizeMake(0.0, shOffset))
      Declare Sub setShadowOpacity Lib "Cocoa" Selector "setShadowOpacity:" (receiver As Ptr, val As Single)
      setShadowOpacity(objectlayer, shOpacity)
      Declare Sub setShadowColor Lib "Cocoa" Selector "setShadowColor:" (receiver As Ptr, val As Ptr)
      setShadowColor(objectlayer, gPresetClr("black"))
      Declare Sub setShadowRadius Lib "Cocoa" Selector "setShadowRadius:" (receiver As Ptr, val As CGFloat)
      setShadowRadius(objectlayer, shRadius)
      
      return objectlayer
    End Function
    
  8. 以下をWindow1にペースト
    Protected Sub MakeIconView(flg As Boolean)
      if flg then  // 初期値に戻すなら
        
        // デフォルト値のセット
        myRadioButton1.Value = true         // Internal [ myRadioButton2 = Dropped File ]
        myRadioButton3.Value=true           // Plane [ myRadioButton4 = H Split, myRadioButton5 = V Split ]
        TextFieldBY.Text = "512.0"          // H Split y
        ClrCanvas1.pClr = RGB(255,255,255)  // H Split Color ( Up or Rgt )
        TextFieldBX.Text = "512.0"          // H Split x
        ClrCanvas2.pClr = RGB(255,255,255)  // V Split Color ( Lo or Lft )
        CheckBox1.Value = false             // Fringe
        TextFieldBW.Text = "64.0"           // Fringe Width
        ClrCanvas3.pClr = RGB(255,255,255)  // Fringe Color
        
        TextFieldSO.Text = "-10.0"          // Shadow Offset
        TextFieldSR.Text = "11.0"           // Shadow Radius
        TextFieldSP.Text = "0.3"            // Shadow Opacity
        
        TextFieldGS.Text = "0.0"            // Gradient StartPoint
        TextFieldGE.Text = "1.0"            // Gradient EndPoint
        TextFieldGO.Text = "0.08"           // Gradient Opacity
        
        TextFieldTX.Text = "100.0"          // Font x
        TextFieldTY.Text = "422.0"          // Font y
        ComboBox1.SelectedRowIndex=0        // Font Name
        TextFieldTF.Text = "240.0"          // Font Size
        TextFieldTS.Text = ""               // String
        ClrCanvas4.pClr = RGB(0,0,0)        // Font Color
        
        TextAreaPP.Text = ""                // Picture File Path
        
        ClrCanvas1.Refresh
        ClrCanvas2.Refresh
        ClrCanvas3.Refresh
        ClrCanvas4.Refresh
        
      end if
      
      // ベース画像の種別
      Dim vv1 As Integer
      if myRadioButton1.Value then
        if myRadioButton3.Value then
          vv1=0
        elseif myRadioButton4.Value then
          vv1=1
        else
          vv1=2
        end if
      else
        vv1=3
      end if
      
      Dim bx As CGFloat = Val(TextFieldBX.Text)
      Dim by As CGFloat = Val(TextFieldBY.Text)
      Dim c1 As Ptr     = gClrCtoG(ClrCanvas1.pClr)  // CGColor
      Dim c2 As Ptr     = gClrCtoG(ClrCanvas2.pClr)  // CGColor
      
      Dim fr As Boolean = CheckBox1.Value
      Dim fw As CGFloat = Val(TextFieldBW.Text)
      Dim fc As Ptr     = gClrCtoG(ClrCanvas3.pClr)  // CGColor
      
      Dim so As CGFloat  = Val(TextFieldSO.Text)
      Dim sr As CGFloat  = Val(TextFieldSR.Text)
      Dim sp As Single   = Val(TextFieldSP.Text)
      
      Dim gs As CGFloat  = Val(TextFieldGS.Text)
      Dim ge As CGFloat  = Val(TextFieldGE.Text)
      Dim go As Single   = Val(TextFieldGO.Text)
      
      Dim tx As CGFloat  = Val(TextFieldTX.Text)
      Dim ty As CGFloat  = Val(TextFieldTY.Text)
      Dim tn As String   = ComboBox1.Text
      Dim ts As CGFloat = Val(TextFieldTF.Text)
      Dim tt As String  = TextFieldTS.Text
      Dim tc As Ptr     = gClrCtoP(ClrCanvas4.pClr)  // NSColor
      
      Dim pp As String = TextAreaPP.Text
      
      // アイコンビュー生成
      MakeIconView2(pViewInst,vv1,bx,by,c1,c2,pPath,fr,fw,fc,so,sr,sp,gs,ge,go,tx,ty,tn,ts,tt,tc,pp)
    End Sub
    
  9. 以下をWindow1にペースト
    Protected Sub MakeIconView2(view As Ptr, mode As Integer, bsX As Integer, bsY As Integer, bsClr1 As Ptr, bsClr2 As Ptr, path As String, rimMode As Boolean, rimWth As Integer, rimClr As Ptr, shOffset As CGFloat, shRadius As CGFloat, shOpacity As Single, grStPnt As CGFloat, grEdPnt As CGFloat, grOpacity As Single, chCodx As CGFloat, chCody As CGFloat, chFname As String, chFsize As CGFloat, chText As String, chClr As Ptr, pcPath As String)
      // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。
      Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
      
      Declare Sub addSublayer Lib "Cocoa" Selector "addSublayer:" (receiver As Ptr, layer As Ptr)
      Dim layer2 As Ptr
      
      // IconViewの生成
      Dim iconView As Ptr = NSClassFromString("NSView")
      Declare Function alloc Lib "Cocoa" selector "alloc" (class_id As Ptr) As Ptr
      iconView = alloc(iconView)
      Declare Function initWithFrame Lib "Cocoa" Selector "initWithFrame:" (receiver As Ptr, identifier As NSRect) As Ptr
      iconView = initWithFrame(iconView, NSMakeRect(0, 0, 1024, 1024))
      Declare Sub setWantsLayer Lib "Cocoa" Selector "setWantsLayer:" (receiver As Ptr, val As Boolean)
      setWantsLayer(iconView, true)
      
      // IconViewからレイヤー(layer1)を取得
      Declare Function layer Lib "Cocoa" Selector "layer" (receiver As Ptr) As Ptr
      Dim layer1 As Ptr = layer(iconView)
      
      // layer1にシャドウレイヤーを追加
      layer2=MakeLayerShadow(shOffset,shRadius,shOpacity)
      addSublayer(layer1, layer2)
      
      // layer1にベースレイヤーを追加
      Dim delta As CGFloat = 0.0
      if rimMode then  // 縁付きの場合は、外周の透過域にベース色が現れる場合があるので、若干小さめに作る(縁の透過域は残るので、サイズ的には問題ない筈)
        delta=1.0  // 1.0以下のRimは描けないことになるが、実用上は差し支えない?
      end if
      layer2=MakeLayerBase(shOffset,shRadius,shOpacity,bsClr1,delta,mode,path,bsClr2,bsX,bsY)
      addSublayer(layer1, layer2)
      
      // layer1に縁レイヤーを追加
      if rimMode then
        layer2=MakeLayerRim(bsClr1,rimWth,rimClr)
        addSublayer(layer1, layer2)
      end if
      
      // layer1にテキストレイヤーを追加
      if chText<>"" then
        layer2=MakeLayerText(chCodx,chCody,chFname,chFsize,chText,chClr)
        addSublayer(layer1, layer2)
      end if
      
      // layer1に画像レイヤーを追加
      if pcPath<>"" then
        layer2=MakeLayerPict(pcPath,shOffset,shRadius,shOpacity)
        addSublayer(layer1, layer2)
      end if
      
      // layer1にグラデーションレイヤーを追加
      layer2=MakeLayerGradient(pcPath,grStPnt,grEdPnt,grOpacity)
      addSublayer(layer1, layer2)
      
      // まず、以前のIconViewを削除
      RemoveIconView(view)
      
      // IconViewを表示用ビューに追加
      Declare Sub addSubview Lib "Cocoa" selector "addSubview:" (class_id As Ptr, view As Ptr)
      addSubview(view, iconView)
      
      // clean up
      Declare Sub release Lib "Cocoa" selector "release" (class_id As Ptr)
      release(iconView)
      
      // 表示用ビューを再描画
      Declare Sub display Lib "Cocoa" selector "display" (class_id As Ptr)
      display(view)
    End Sub
    
  10. 以下をWindow1にペースト
    Protected Function MakeLayerBase(shOffset As CGFloat, shRadius As CGFloat, shOpacity As Single, bsClr1 As Ptr, delta As CGFloat, mode As Integer, path As String, bsClr2 As Ptr, bsX As Integer, bsY As Integer) As Ptr
      // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。
      Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
      
      // 矩形を角丸にするマスク
      Dim masklayer As Ptr = NSClassFromString("CALayer")
      Declare Function layer Lib "Cocoa" Selector "layer" (receiver As Ptr) As Ptr
      masklayer = layer(masklayer)
      Declare Sub setBackgroundColor Lib "Cocoa" Selector "setBackgroundColor:" (receiver As Ptr, val As Ptr)
      setBackgroundColor(masklayer, gPresetClr("black"))
      Declare Sub setFrame Lib "Cocoa" Selector "setFrame:" (receiver As Ptr, rect As CGRect)
      setFrame(masklayer, CGRectMake(100+delta, 100+delta, 824-delta*2, 824-delta*2))
      Declare Sub setCornerRadius Lib "Cocoa" Selector "setCornerRadius:" (receiver As Ptr, val As CGFloat)
      setCornerRadius(masklayer, 184.0)  // 現物合わせの調整値
      Declare Sub setCornerCurve Lib "Cocoa" Selector "setCornerCurve:" (receiver As Ptr, val As CFStringRef)
      setCornerCurve(masklayer, "continuous")  // kCACornerCurveContinuous = スムースカーブ
      Declare Sub addSublayer Lib "Cocoa" Selector "addSublayer:" (receiver As Ptr, layer As Ptr)
      
      Declare Sub setMask Lib "Cocoa" Selector "setMask:" (receiver As Ptr, msk As Ptr)
      
      // ベース矩形
      select case mode
      case 0  // 単色矩形
        
        Dim baselayer As Ptr = NSClassFromString("CALayer")
        baselayer = layer(baselayer)
        setBackgroundColor(baselayer, bsClr1)
        setFrame(baselayer, CGRectMake(0, 0, 1024, 1024))
        setMask(baselayer, masklayer)
        
        return baselayer
        
      case 1, 2  // 分割
        
        // 矩形(上または右)
        Dim splitlayer1 As Ptr = NSClassFromString("CALayer")
        splitlayer1 = layer(splitlayer1)
        setBackgroundColor(splitlayer1, bsClr1)
        if mode=1 then
          setFrame(splitlayer1, CGRectMake(0, bsY, 1024, 1024-bsY))
        else
          setFrame(splitlayer1, CGRectMake(bsX, 0, 1024-bsX, 1024))
        end if
        // 矩形(下または左)
        Dim splitlayer2 As Ptr = NSClassFromString("CALayer")
        splitlayer2 = layer(splitlayer2)
        setBackgroundColor(splitlayer2, bsClr2)
        if mode=1 then
          setFrame(splitlayer2, CGRectMake(0, 0, 1024, bsY))
        else
          setFrame(splitlayer2, CGRectMake(0, 0, bsX, 1024))
        end if
        setMask(splitlayer2, masklayer)  // splitlayer2にmasklayerを追加
        addSublayer(splitlayer2, splitlayer1)  // splitlayer2にsplitlayer1を追加
        
        return splitlayer2
        
      case 3  // Dropped File
        
        Dim imagelayer As Ptr = NSClassFromString("CALayer")
        imagelayer = layer(imagelayer)
        Dim image0 As Ptr = NSClassFromString("NSImage")
        Declare Function alloc Lib "Cocoa" selector "alloc" (class_id As Ptr) As Ptr
        image0 = alloc(image0)
        Declare Function initWithContentsOfFile Lib "Cocoa" Selector "initWithContentsOfFile:" (receiver As Ptr, name As CFStringRef) As Ptr  // 画像をファイルから読み込み
        image0 = initWithContentsOfFile(image0, path)
        Declare Function myCGImage Lib "Cocoa" Selector "CGImage" (receiver As Ptr) As Ptr
        Dim cgimg As Ptr = myCGImage(image0)
        Declare Sub setContents Lib "Cocoa" Selector "setContents:" (receiver As Ptr, cont As Ptr)
        setContents(imagelayer, cgimg)
        setFrame(imagelayer, CGRectMake(0, 0, 1024, 1024))
        setMask(imagelayer, masklayer)
        Declare Sub release Lib "Cocoa" Selector "release" (receiver As Ptr)
        release(image0)
        
        return imagelayer
        
      end select
    End Function
    
  11. 以下をWindow1にペースト
    Protected Function MakeLayerGradient(pcPath As String, grStPnt As CGFloat, grEdPnt As CGFloat, grOpacity As Single) As Ptr
      // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。
      Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
      
      // ベースを画像ファイル化したもの取り込み、マスクの初期値とする
      Dim f As FolderItem = SpecialFolder.Resources.Child("gradientMask.png")
      Dim masklayer As Ptr = NSClassFromString("CALayer")
      Declare Function layer Lib "Cocoa" Selector "layer" (receiver As Ptr) As Ptr
      masklayer = layer(masklayer)
      Dim image00 As Ptr = NSClassFromString("NSImage")
      Declare Function alloc Lib "Cocoa" selector "alloc" (class_id As Ptr) As Ptr
      image00 = alloc(image00)
      Declare Function initWithContentsOfFile Lib "Cocoa" Selector "initWithContentsOfFile:" (receiver As Ptr, name As CFStringRef) As Ptr
      image00 = initWithContentsOfFile(image00, f.NativePath)
      Declare Function myCGImage Lib "Cocoa" Selector "CGImage" (receiver As Ptr) As Ptr
      Dim cgimg00 As Ptr = myCGImage(image00)
      Declare Sub setContents Lib "Cocoa" Selector "setContents:" (receiver As Ptr, cont As Ptr)
      setContents(masklayer, cgimg00)
      Declare Sub setFrame Lib "Cocoa" Selector "setFrame:" (receiver As Ptr, rect As CGRect)
      setFrame(masklayer, CGRectMake(0, 0, 1024, 1024))
      Declare Sub release Lib "Cocoa" Selector "release" (receiver As Ptr)
      release(image00)
      
      // ベースをはみ出したピクチャーもグラデーションの対象とするため、ピクチャーをマスクとしてマージする
      if pcPath<>"" then
        
        // ピクチャーをセットした矩形
        Dim imagelayer As Ptr = NSClassFromString("CALayer")
        imagelayer = layer(imagelayer)
        Dim image0 As Ptr = NSClassFromString("NSImage")
        image0 = alloc(image0)
        image0 = initWithContentsOfFile(image0, pcPath)
        Dim cgimg As Ptr = myCGImage(image0)
        setContents(imagelayer, cgimg)
        setFrame(imagelayer, CGRectMake(0, 0, 1024, 1024))
        release(image0)
        Declare Sub addSublayer Lib "Cocoa" Selector "addSublayer:" (receiver As Ptr, layer As Ptr)
        addSublayer(masklayer, imagelayer)
        
      end if
      
      // 全面にかかる透明度の高いグラデーション
      Dim gradientLayer As Ptr = NSClassFromString("CAGradientLayer")
      gradientLayer = layer(gradientLayer)
      setFrame(gradientLayer, CGRectMake(0, 0, 1024, 1024))
      Dim colors As Ptr = NSClassFromString("NSMutableArray")
      Declare Function myArray Lib "Cocoa" Selector "array" (receiver As Ptr) As Ptr
      colors = myArray(colors)
      Declare Sub addObject Lib "Cocoa" Selector "addObject:" (receiver As Ptr, obj As Ptr)
      addObject(colors, gPresetClr("black"))
      addObject(colors, gPresetClr("white"))
      Declare Sub setColors Lib "Cocoa" Selector "setColors:" (receiver As Ptr, val As Ptr)
      setColors(gradientLayer, colors)
      Declare Sub setStartPoint Lib "Cocoa" Selector "setStartPoint:" (receiver As Ptr, val As CGPoint)
      setStartPoint(gradientLayer, CGPointMake(0.0, grStPnt))
      Declare Sub setEndPoint Lib "Cocoa" Selector "setEndPoint:" (receiver As Ptr, val As CGPoint)
      setEndPoint(gradientLayer, CGPointMake(0.0, grEdPnt))
      Declare Sub setOpacity Lib "Cocoa" Selector "setOpacity:" (receiver As Ptr, val As Single)
      setOpacity(gradientLayer, grOpacity)
      Declare Sub setMask Lib "Cocoa" Selector "setMask:" (receiver As Ptr, msk As Ptr)
      setMask(gradientLayer, masklayer)
      
      return gradientLayer
    End Function
    
  12. 以下をWindow1にペースト
    Protected Function MakeLayerPict(pcPath As String, shOffset As CGFloat, shRadius As CGFloat, shOpacity As Single) As Ptr
      // パスで指定されたファイルの実在チェック
      Dim f As FolderItem = new FolderItem(pcPath)
      if f=nil or f.Exists=false then
        GenMsgDlg "Pictureファイルが見つかりません。",pcPath
        return nil
      end if
      
      // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。
      Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
      
      // ピクチャーをセットした矩形
      Dim imagelayer As Ptr = NSClassFromString("CALayer")
      Declare Function layer Lib "Cocoa" Selector "layer" (receiver As Ptr) As Ptr
      imagelayer = layer(imagelayer)
      Dim image0 As Ptr = NSClassFromString("NSImage")
      Declare Function alloc Lib "Cocoa" selector "alloc" (class_id As Ptr) As Ptr
      image0 = alloc(image0)
      Declare Function initWithContentsOfFile Lib "Cocoa" Selector "initWithContentsOfFile:" (receiver As Ptr, name As CFStringRef) As Ptr  // 画像をファイルから読み込み
      image0 = initWithContentsOfFile(image0, pcPath)
      Declare Function myCGImage Lib "Cocoa" Selector "CGImage" (receiver As Ptr) As Ptr
      Dim cgimg As Ptr = myCGImage(image0)
      Declare Sub setContents Lib "Cocoa" Selector "setContents:" (receiver As Ptr, cont As Ptr)
      setContents(imagelayer, cgimg)
      Declare Sub setFrame Lib "Cocoa" Selector "setFrame:" (receiver As Ptr, rect As CGRect)
      setFrame(imagelayer, CGRectMake(0, 0, 1024, 1024))
      Declare Sub release Lib "Cocoa" Selector "release" (receiver As Ptr)
      release(image0)
      
      // ベースと同じシャドウ属性を付加
      imagelayer = MakeAttrShadow(imagelayer,shOffset,shRadius,shOpacity)
      
      return imagelayer
    End Function
    
  13. 以下をWindow1にペースト
    Protected Function MakeLayerRim(bsClr As Ptr, rimWth As Integer, rimClr As Ptr) As Ptr
      // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。
      Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
      
      // 角丸矩形マスク
      Dim masklayer As Ptr = NSClassFromString("CALayer")
      Declare Function layer Lib "Cocoa" Selector "layer" (receiver As Ptr) As Ptr
      masklayer = layer(masklayer)
      Declare Sub setBackgroundColor Lib "Cocoa" Selector "setBackgroundColor:" (receiver As Ptr, val As Ptr)
      setBackgroundColor(masklayer, gPresetClr("black"))
      Declare Sub setFrame Lib "Cocoa" Selector "setFrame:" (receiver As Ptr, rect As CGRect)
      setFrame(masklayer, CGRectMake(0, 0, 824, 824))
      Declare Sub setCornerRadius Lib "Cocoa" Selector "setCornerRadius:" (receiver As Ptr, val As CGFloat)
      setCornerRadius(masklayer, 184)  // 現物合わせの調整値
      Declare Sub setCornerCurve Lib "Cocoa" Selector "setCornerCurve:" (receiver As Ptr, val As CFStringRef)
      setCornerCurve(masklayer, "continuous")  // kCACornerCurveContinuous = スムースカーブ
      
      // 角丸矩形(領域内は透明、境界線を縁とする)
      Dim rimlayer As Ptr = NSClassFromString("CALayer")
      rimlayer = layer(rimlayer)
      setBackgroundColor(rimlayer, gPresetClr("clear"))
      setFrame(rimlayer, CGRectMake(100, 100, 824, 824))
      setCornerRadius(rimlayer, 184)  // 現物合わせの調整値
      setCornerCurve(rimlayer, "continuous")  // kCACornerCurveContinuous = スムースカーブ
      Declare Sub setBorderWidth Lib "Cocoa" Selector "setBorderWidth:" (receiver As Ptr, val As CGFloat)
      setBorderWidth(rimlayer, rimWth)  // 境界線の幅
      Declare Sub setBorderColor Lib "Cocoa" Selector "setBorderColor:" (receiver As Ptr, val As Ptr)
      setBorderColor(rimlayer, rimClr)  // 境界線のカラー
      
      // rimlayerにmasklayerを追加
      Declare Sub setMask Lib "Cocoa" Selector "setMask:" (receiver As Ptr, msk As Ptr)
      setMask(rimlayer, masklayer)
      
      return rimlayer
    End Function
    
  14. 以下をWindow1にペースト
    Protected Function MakeLayerShadow(shOffset As CGFloat, shRadius As CGFloat, shOpacity As Single) As Ptr
      // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。
      Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
      
      // シャドウ付加用の角丸矩形(矩形自体は不要だが、シャドウをつけるために必要)
      Dim shadowlayer As Ptr = NSClassFromString("CALayer")
      Declare Function layer Lib "Cocoa" Selector "layer" (receiver As Ptr) As Ptr
      shadowlayer = layer(shadowlayer)
      Declare Sub setBackgroundColor Lib "Cocoa" Selector "setBackgroundColor:" (receiver As Ptr, val As Ptr)
      setBackgroundColor(shadowlayer, gPresetClr("white"))
      Declare Sub setFrame Lib "Cocoa" Selector "setFrame:" (receiver As Ptr, rect As CGRect)
      setFrame(shadowlayer, CGRectMake(100, 100, 824, 824))
      Declare Sub setCornerRadius Lib "Cocoa" Selector "setCornerRadius:" (receiver As Ptr, val As CGFloat)
      setCornerRadius(shadowlayer, 184.0)
      Declare Sub setCornerCurve Lib "Cocoa" Selector "setCornerCurve:" (receiver As Ptr, val As CFStringRef)
      setCornerCurve(shadowlayer, "continuous")  // kCACornerCurveContinuous = スムースカーブ
      
      // シャドウ属性を付加
      shadowlayer = MakeAttrShadow(shadowlayer,shOffset,shRadius,shOpacity)
      
      return shadowlayer
    End Function
    
  15. 以下をWindow1にペースト
    Protected Function MakeLayerText(chCodx As CGFloat, chCody As CGFloat, chFname As String, chFsize As CGFloat, chText As String, chClr As Ptr) As Ptr
      // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。
      Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
      
      // テキスト
      //(Frameのwidthをベース幅、揃えを中央にしているので、始点位置は直感的でない。必要なら文字列の幅を取得してwidthにセットする等して下さい。)
      //(Frameのheightをフォントサイズとしているが、フォントによっては不足する場合がある。必要なら文字列の高さを取得してheightにセットする等して下さい。)
      Dim textLayer As Ptr = NSClassFromString("CATextLayer")
      Declare Function layer Lib "Cocoa" Selector "layer" (receiver As Ptr) As Ptr
      textLayer = layer(textLayer)
      Declare Sub setFrame Lib "Cocoa" Selector "setFrame:" (receiver As Ptr, rect As CGRect)
      setFrame(textLayer, CGRectMake(chCodx, chCody, 824, chFsize))
      Declare Sub setString Lib "Cocoa" Selector "setString:" (receiver As Ptr, val As CFStringRef)
      setString(textLayer, chText)
      Declare Sub setForegroundColor Lib "Cocoa" Selector "setForegroundColor:" (receiver As Ptr, val As Ptr)
      setForegroundColor(textLayer, chClr)
      Dim font As Ptr = NSClassFromString("NSFont")
      Declare Function systemFontOfSize Lib "Cocoa" Selector "fontWithName:size:" (receiver As Ptr, name As CFStringRef, size As CGFloat) As Ptr
      font = systemFontOfSize(font, chFname, chFsize)
      Declare Sub setFont Lib "Cocoa" Selector "setFont:" (receiver As Ptr, val As Ptr)
      setFont(textLayer, font)
      Declare Sub setFontSize Lib "Cocoa" Selector "setFontSize:" (receiver As Ptr, val As CGFloat)
      setFontSize(textLayer, chFsize)
      Declare Sub setAlignmentMode Lib "Cocoa" Selector "setAlignmentMode:" (receiver As Ptr, val As CFStringRef)
      setAlignmentMode(textLayer, "center")
      
      return textLayer
    End Function
    
  16. 以下をWindow1にペースト
    Protected Sub SaveParam(flg As Boolean)
      Var f As FolderItem
      if flg then
        Dim nam As String = "AppIconMakeParam.txt"
        if pFIparam <> Nil Then
          nam = pFIparam.Name
        end if
        f=FolderItem.ShowSaveFileDialog("text/plain", nam)
      else
        f=pFIparam
      end if
      If f = Nil Then
        return
      End If
      
      if pFIparam<>f then
        pFIparam=f
      end if
      
      Try
        
        Var t As TextOutputStream = TextOutputStream.Create(f)
        
        Dim vv As String
        if myRadioButton1.Value then
          vv="0"
        else
          vv="1"
        end if
        t.WriteLine(vv)  // 1,2
        if myRadioButton3.Value then
          vv="0"
        elseif myRadioButton4.Value then
          vv="1"
        else
          vv="2"
        end if
        t.WriteLine(vv)  // 3,4,5
        t.WriteLine(TextFieldBY.Text)
        t.WriteLine(Str(gClrCtoI(ClrCanvas1.pClr)))
        t.WriteLine(TextFieldBX.Text)
        t.WriteLine(Str(gClrCtoI(ClrCanvas2.pClr)))
        
        t.WriteLine(Str(CheckBox1.Value))
        
        t.WriteLine(TextFieldBW.Text)
        t.WriteLine(Str(gClrCtoI(ClrCanvas3.pClr)))
        
        t.WriteLine(TextFieldSO.Text)
        t.WriteLine(TextFieldSR.Text)
        t.WriteLine(TextFieldSP.Text)
        
        t.WriteLine(TextFieldGS.Text)
        t.WriteLine(TextFieldGE.Text)
        t.WriteLine(TextFieldGO.Text)
        
        t.WriteLine(TextFieldTX.Text)
        t.WriteLine(TextFieldTY.Text)
        t.WriteLine(ComboBox1.Text)
        t.WriteLine(TextFieldTF.Text)
        t.WriteLine(TextFieldTS.Text)
        t.WriteLine(Str(gClrCtoI(ClrCanvas4.pClr)))
        
        t.WriteLine(pPath)  // Dropped File Path
        
        t.WriteLine(TextAreaPP.Text)  // Picture File Path
        
        t.Close
        
      Catch e As IOException
        ' handle error
      End Try
    End Sub
    
  17. 以下をGlobalsにペースト
    Public Function gClrCtoG(clrC As Color) As Ptr
      // Color型をNSColor型に変換
      Dim clrP As Ptr = gClrCtoP(clrC)
      
      // NSColor型をCGColor型に変換
      Declare Function myCGColor Lib "Cocoa" Selector "CGColor" (receiver As Ptr) As Ptr
      Dim clrG As Ptr = myCGColor(clrP)
      
      return clrG
    End Function
    
  18. 以下をGlobalsにペースト
    Public Function gPresetClr(name As String) As Ptr
      // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。
      Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
      
      Dim clrPtr As Ptr = NSClassFromString("NSColor")
      
      select case name
      case "white"
        
        Declare Function whiteColor Lib "Cocoa" Selector "whiteColor" (receiver As Ptr) As Ptr
        clrPtr = whiteColor(clrPtr)
        
      case "black"
        
        Declare Function blackColor Lib "Cocoa" Selector "blackColor" (receiver As Ptr) As Ptr
        clrPtr = blackColor(clrPtr)
        
      case "clear"
        
        Declare Function clearColor Lib "Cocoa" Selector "clearColor" (receiver As Ptr) As Ptr
        clrPtr = clearColor(clrPtr)
        
      end select
      
      Declare Function myCGColor Lib "Cocoa" Selector "CGColor" (receiver As Ptr) As Ptr
      clrPtr = myCGColor(clrPtr)
      
      return clrPtr
    End Function
    
  19. 新規ファイルタイプグループ(名前は、ここでは「FileTypeGroup1」)を作成する。
  20. FileTypeGroup1で、Add Common File TypeMore...で表示されるSelect the file type to add:で、ポップアップメニューからimage/gif, image/jpeg, image/png, image/tiff, text/plainを追加。(必要なら更に追加して下さい。)

  21. プロジェクト左ペインのBuild Settings>macOS上で右クリックし、Add to "Build Settings">Build Step>Copy Filesを選択
  22. InspectorのDestinationにResources Folderを指定
  23. 中央ペインに、gradientMask.png(以下を予めダウンロードして、任意の場所に置いておく)をドラッグ&ドロップ
    S Shot1
    gradientMask.png
 実行してみたところ、ツール画像を付加したアプリアイコンを作成できることを確認しました。
S Shot2
(クリックで拡大)
 試作したサンプルは、以下の通りです。
S Shot431 S Shot432 S Shot433 S Shot434 S Shot435 S Shot436
icon_32x32@2x.png相当を、72dpiで書き出したものです。
(注:あくまでサンプルのため、ツール画像の質はアップルのガイドラインとは無関係です。)

 おわりに

 ツール画像は一個に限定されますが、完全に内包される画像やテキストは何個あってもいい筈なので、画像ソフトのように、複数レイヤーを持てるようにしてもいいかもしれません。

 あと、グラデーションが綺麗にかからない問題の完全解消が課題として残っていますが、これはなんとも。
(レイヤーの展開(マージ)ができれば、試行の範囲が広がるのですが、やり方があるのかすら分からない...>グラフィックコンテキストに描けばいいじゃん、という話はあるようですが。)


 お世話になったサイト

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

 参考サイト(1):アプリアイコン | Apple Developer Documentation(macOSの項。ガイド図はこちら
 参考サイト(2):objective c - How to get the width of an NSString? - Stack Overflow
 参考サイト(3):How to calculate the height of an NSAttributedString with given width in iOS 6 - Stack Overflow


 更新履歴

 2025.03.07 新規作成


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