ホームページ>開発ツール>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数)を超えるものが該当。)座標値の話が出たので、ここでグラデーションのy座標についても述べておきます。(注:x座標はここでは考えなくてよい。)
参考サイト(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
Cocoa(macOS)の座標系はLLOなので、y座標の原点はViewの下端(ウィンドウ下端から10pixel上)にあります。グラデーションのStartPointは、ここからの距離になります。(そのため、厳密にアイコン下辺から始めたい場合は、余白の100pixel分ずらす必要がある。)
一方、カラーは、StartPointが黒、EndPointは白、としていますので、グレースケールとなります。
色の変化はStartPointから始まり、EndPointで終了します。なので、StartPointの方がEndPointより数値が大きいと、上部が暗くなります。
以上を踏まえ、(残りの)仕様は以下の通りとしました。
- ツール画像は1024x1024pixelとし、ツール位置はあらかじめ合わせ込んでおくものとする。
- ドロップシャドウのパラメーターは、ベースとツールで同じものを使う。
- パラメーター保存ファイルの最後に、ツール用画像ファイルのパスを追加する。(旧ファイルとの互換性を維持)
- グラデーションのマスク用画像ファイルは、アプリのResourcesフォルダーに置く。
- ツール画像は、ダイアログで指定する。(ベース画像がドラッグ&ドロップなので、統一感はなくなる。必要なら変更して下さい。)
- これまで抜けていた、ファイルタイプグループの設定も行う。(注:画像ファイルのドラッグ&ドロップには適用されない。詳細はこちら。)
Xojoでの実装
注:以下の実装では、一部Xojo 2022r4.1ではDeprecatedな機能が使われています。必要なら推奨される機能に置き換えて下さい。
【ソースコードのコピー&ペーストについて】
・ソースコード(グレー背景部分の全文)をコピーし、指定のオブジェクトにペーストすると、(新規作成して名前等を個別にコピー&ペーストしなくても)復元されます。
・ペーストはオブジェクトに行って下さい。オブジェクト内のEvent Handlers/Methods/Properties等にペーストしても、うまくいかない場合があります。
・それでもペーストできない場合は、各項目のカッコ内を適用して下さい。
実行してみたところ、ツール画像を付加したアプリアイコンを作成できることを確認しました。
- 前回プロジェクト(フォントパネル対応版)をベースとする
- Window1に、DesktopBevelButton1個(Name:BevelButton2, Caption:Get...)、DesktopTextArea1個(Name:TextAreaPP)を置く。(配置はスクリーンショット参照)
- 以下を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
- 以下の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
- Window1のメソッドのうち、FontSelected, GenMsgDlg, RemoveIconView, ShowFontPanelは残す。MakeIcns>ExportIcns、MakeIcnsShell>ExportIcnsShell、SaveImage>ExportImageにリネーム(呼び出し箇所も合わせる)。残りは削除。
- 以下を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
- 以下を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
- 以下を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
- 以下を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
- 以下を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
- 以下を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
- 以下を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
- 以下を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
- 以下を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
- 以下を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
- 以下を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
- 以下を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
- 以下を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
- 新規ファイルタイプグループ(名前は、ここでは「FileTypeGroup1」)を作成する。
- FileTypeGroup1で、Add Common File Type>More...で表示されるSelect the file type to add:で、ポップアップメニューからimage/gif, image/jpeg, image/png, image/tiff, text/plainを追加。(必要なら更に追加して下さい。)
- プロジェクト左ペインのBuild Settings>macOS上で右クリックし、Add to "Build Settings">Build Step>Copy Filesを選択
- InspectorのDestinationにResources Folderを指定
- 中央ペインに、gradientMask.png(以下を予めダウンロードして、任意の場所に置いておく)をドラッグ&ドロップ
gradientMask.png
試作したサンプルは、以下の通りです。
(クリックで拡大)
![]()
![]()
![]()
![]()
![]()
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]